diff --git a/gsd-core/bin/lib/state-transition.cjs b/gsd-core/bin/lib/state-transition.cjs index 12937e4ad..e047f9428 100644 --- a/gsd-core/bin/lib/state-transition.cjs +++ b/gsd-core/bin/lib/state-transition.cjs @@ -226,7 +226,7 @@ function beginPhaseCore(content, intent, deps) { const reassemble = (b) => hasFrontmatter ? `---\n${reconstructFrontmatter(existingFm)}\n---\n\n${b}` : b; - const today = deps.clock.today(); + const today = deps.clock.localToday(); // Consult the field-classification table for the frontmatter keys this // transition touches (codex Phase 1 review: "table not consulted by // transitionCore"). The table tracks FRONTMATTER keys (lowercase: `status`, @@ -502,7 +502,7 @@ function mutateCurrentPositionForAdvance(content, fields, statusDefaults, lastAc * adapter to construct CLI output. */ function advancePlanCore(content, deps) { - const today = deps.clock.today(); + const today = deps.clock.localToday(); // #1255: body-field replacements operate on body only (frontmatter stripped), // not on the full content. The YAML `status:` key matches `^Status:\s*` // before the body field if full content is passed (codex Phase 2 review: @@ -608,7 +608,7 @@ function advancePlanCore(content, deps) { */ function completePhaseCore(content, intent, deps) { const updated = []; - const today = deps.clock.today(); + const today = deps.clock.localToday(); // Consult the field-classification table for the frontmatter keys this // transition touches (same guard beginPhaseCore applies). A missing row is a // substrate defect — fail loudly rather than silently re-encoding policy. @@ -762,7 +762,7 @@ function completePhaseCore(content, intent, deps) { */ function plannedPhaseCore(content, intent, deps) { const updated = []; - const today = deps.clock.today(); + const today = deps.clock.localToday(); for (const fmKey of ['status', 'last_activity', 'last_activity_desc']) { const cls = getFieldClassification(fmKey); if (cls === null) { @@ -840,7 +840,7 @@ function plannedPhaseCore(content, intent, deps) { * directly and must not run the steady-state `syncStateFrontmatter` post-sync. */ function milestoneSwitchCore(content, intent, deps) { - const today = deps.clock.today(); + const today = deps.clock.localToday(); const updated = [ 'milestone', 'milestone_name', @@ -927,7 +927,7 @@ function milestoneSwitchCore(content, intent, deps) { */ function milestoneCompleteCore(content, intent, deps) { const updated = []; - const today = deps.clock.today(); + const today = deps.clock.localToday(); const version = intent.version; for (const fmKey of ['status', 'last_activity', 'last_activity_desc']) { const cls = getFieldClassification(fmKey); @@ -1175,7 +1175,7 @@ function pruneCore(content, intent) { * (rather than silently writing fallback-derived wrong values). */ function syncCore(content, intent, deps) { - const today = deps.clock.today(); + const today = deps.clock.localToday(); const changes = []; let modified = content; const updated = []; diff --git a/src/commands.cts b/src/commands.cts index c1d38cdf8..7f2704197 100644 --- a/src/commands.cts +++ b/src/commands.cts @@ -1431,6 +1431,9 @@ function cmdTodoComplete(cwd: string, filename: string | undefined, raw: boolean function cmdScaffold(cwd: string, type: string, options: ScaffoldOptions, raw: boolean): void { const { phase, name } = options; const padded = phase ? normalizePhaseName(phase) : '00'; + // #2136 sibling site (deliberately deferred per the issue's scope): scaffold's + // date stays on the raw UTC slice for now; route through realClock.localToday() + // alongside workstream.cts/gsd2-import.cts/template.cts/verify.cts in a follow-up. const today = realClock.localToday(); // Find phase directory diff --git a/src/gsd2-import.cts b/src/gsd2-import.cts index daf0852d6..acb245bd3 100644 --- a/src/gsd2-import.cts +++ b/src/gsd2-import.cts @@ -23,6 +23,7 @@ import fs from 'node:fs'; import path from 'node:path'; import { platformWriteSync } from './shell-command-projection.cjs'; import { formatGsdSlash, resolveRuntime } from './runtime-slash.cjs'; +import { realClock } from './clock.cjs'; // eslint-disable-next-line @typescript-eslint/no-require-imports import ioMod = require('./io.cjs'); const { output } = ioMod; @@ -351,7 +352,7 @@ function buildStateMd(phaseMap: PhaseMapEntry[]): string { const filled = Math.round(pct / 10); const bar = `[${'█'.repeat(filled)}${'░'.repeat(10 - filled)}]`; - const today = new Date().toISOString().split('T')[0]; + const today = realClock.localToday(); return [ '# Project State', diff --git a/src/init.cts b/src/init.cts index 4b37d2968..0ec0da3f4 100644 --- a/src/init.cts +++ b/src/init.cts @@ -10,6 +10,7 @@ import fs from 'node:fs'; import path from 'node:path'; import os from 'node:os'; import { execGit, platformWriteSync, platformReadSync } from './shell-command-projection.cjs'; +import { realClock } from './clock.cjs'; // eslint-disable-next-line @typescript-eslint/no-require-imports -- io.cjs is an export= CommonJS module import io = require('./io.cjs'); // eslint-disable-next-line @typescript-eslint/no-require-imports -- config-loader.cjs is an export= CommonJS module @@ -820,8 +821,8 @@ function cmdInitQuick(cwd: string, description: string | undefined, raw: boolean slug: slug, description: description || null, - date: now.toISOString().split('T')[0], - timestamp: now.toISOString(), + date: realClock.localToday(), + timestamp: realClock.nowIso(), quick_dir: '.planning/quick', task_dir: slug ? `.planning/quick/${quickId}-${slug}` : null, @@ -1133,7 +1134,6 @@ function cmdInitPhaseOp(cwd: string, phase: string, raw: boolean): void { function cmdInitTodos(cwd: string, area: string | undefined, raw: boolean): void { const config = loadConfig(cwd); - const now = new Date(); const pendingDir = path.join(planningDir(cwd), 'todos', 'pending'); let count = 0; @@ -1176,8 +1176,8 @@ function cmdInitTodos(cwd: string, area: string | undefined, raw: boolean): void const result: Record = { commit_docs: config.commit_docs, - date: now.toISOString().split('T')[0], - timestamp: now.toISOString(), + date: realClock.localToday(), + timestamp: realClock.nowIso(), todo_count: count, todos, @@ -1306,7 +1306,6 @@ function cmdInitMilestoneOp(cwd: string, raw: boolean): void { function cmdInitMapCodebase(cwd: string, raw: boolean): void { const config = loadConfig(cwd); - const now = new Date(); const codebaseDir = path.join(planningRoot(cwd), 'codebase'); let existingMaps: string[] = []; @@ -1324,8 +1323,8 @@ function cmdInitMapCodebase(cwd: string, raw: boolean): void { parallelization: config.parallelization, subagent_timeout: config.subagent_timeout, - date: now.toISOString().split('T')[0], - timestamp: now.toISOString(), + date: realClock.localToday(), + timestamp: realClock.nowIso(), codebase_dir: '.planning/codebase', diff --git a/src/state.cts b/src/state.cts index cdd018080..f7d7b4149 100644 --- a/src/state.cts +++ b/src/state.cts @@ -2582,7 +2582,7 @@ function cmdStatePrune(cwd: string, options: StatePruneOptions, raw: boolean): v // Write archived entries to STATE-ARCHIVE.md if (archived.length > 0) { - const timestamp = realClock.today(); + const timestamp = realClock.localToday(); let archiveContent = platformReadSync(archivePath); if (archiveContent === null) { archiveContent = '# STATE Archive\n\nPruned entries from STATE.md. Recoverable but no longer loaded into agent context.\n\n'; diff --git a/src/template.cts b/src/template.cts index d696b02a8..e4b4bcd74 100644 --- a/src/template.cts +++ b/src/template.cts @@ -8,6 +8,7 @@ import fs from 'node:fs'; import path from 'node:path'; +import { realClock } from './clock.cjs'; // eslint-disable-next-line @typescript-eslint/no-require-imports import ioMod = require('./io.cjs'); const { output, error } = ioMod; @@ -110,7 +111,7 @@ function cmdTemplateFill(cwd: string, templateType: string | null | undefined, o if (!phaseInfo || !phaseInfo.found) { output({ error: 'Phase not found', phase: options.phase }, raw, undefined); return; } const padded = normalizePhaseName(options.phase); - const today = new Date().toISOString().split('T')[0]; + const today = realClock.localToday(); const phaseName = options.name || phaseInfo.phase_name || 'Unnamed'; const phaseSlug = phaseInfo.phase_slug || generateSlugInternal(phaseName); const phaseId = `${padded}-${phaseSlug}`; diff --git a/src/verify.cts b/src/verify.cts index 4695fd5aa..a72621bd9 100644 --- a/src/verify.cts +++ b/src/verify.cts @@ -10,6 +10,7 @@ import fs from 'node:fs'; import path from 'node:path'; import os from 'node:os'; import { phaseVariants, buildRoadmapPhaseVariants, buildNotStartedPhaseVariants } from './validate.cjs'; +import { realClock } from './clock.cjs'; import { phaseDirNameRe, PHASE_TOKEN_FROM_DIR_RE, MILESTONE_ARCHIVE_DIR_RE, canonicalPlanStem } from './validate.cjs'; // eslint-disable-next-line @typescript-eslint/no-require-imports -- planning-workspace.cjs is an export= CommonJS module import planningWorkspace = require('./planning-workspace.cjs'); @@ -1893,7 +1894,7 @@ function cmdValidateHealth( stateContent += `**Current phase:** (determining...)\n`; stateContent += `**Status:** Resuming\n\n`; stateContent += `## Session Log\n\n`; - stateContent += `- ${new Date().toISOString().split('T')[0]}: STATE.md regenerated by ${slash('health')} --repair\n`; + stateContent += `- ${realClock.localToday()}: STATE.md regenerated by ${slash('health')} --repair\n`; writeStateMd(statePath, stateContent, cwd); repairActions.push({ action: repair, success: true, path: 'STATE.md' }); break; @@ -1944,7 +1945,7 @@ function cmdValidateHealth( } case 'backfillMilestones': { if (!options['backfill'] && !options['repair']) break; - const today = new Date().toISOString().split('T')[0]; + const today = realClock.localToday(); let backfilled = 0; for (const ver of missingFromRegistry) { try { diff --git a/src/workstream.cts b/src/workstream.cts index 2ec04089c..437377adc 100644 --- a/src/workstream.cts +++ b/src/workstream.cts @@ -14,6 +14,7 @@ import fs from 'node:fs'; import path from 'node:path'; +import { realClock } from './clock.cjs'; // eslint-disable-next-line @typescript-eslint/no-require-imports import io = require('./io.cjs'); const { output, error } = io; @@ -177,7 +178,7 @@ function cmdWorkstreamCreate(cwd: string, name: string | null | undefined, optio platformEnsureDir(wsDir); platformEnsureDir(path.join(wsDir, 'phases')); - const today = new Date().toISOString().split('T')[0]; + const today = realClock.localToday(); const stateContent = [ '---', `workstream: ${slug}`, @@ -297,7 +298,7 @@ function cmdWorkstreamComplete(cwd: string, name: string | null | undefined, opt if (active === name) setActiveWorkstream(cwd, null as unknown as string); const archiveDir = path.join(root, 'milestones'); - const today = new Date().toISOString().split('T')[0]; + const today = realClock.localToday(); let archivePath = path.join(archiveDir, `ws-${name}-${today}`); let suffix = 1; while (fs.existsSync(archivePath)) {