gsdd-cli 0.22.0 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/bin/gsdd.mjs +3 -2
- package/bin/lib/control-map.mjs +629 -0
- package/bin/lib/init-flow.mjs +7 -7
- package/bin/lib/init-runtime.mjs +3 -0
- package/bin/lib/phase.mjs +230 -2
- package/bin/lib/rendering.mjs +5 -0
- package/bin/lib/ui-proof.mjs +39 -4
- package/distilled/DESIGN.md +39 -0
- package/distilled/EVIDENCE-INDEX.md +10 -0
- package/distilled/README.md +9 -0
- package/distilled/workflows/execute.md +3 -3
- package/distilled/workflows/pause.md +2 -0
- package/distilled/workflows/plan.md +5 -5
- package/distilled/workflows/progress.md +4 -0
- package/distilled/workflows/quick.md +1 -1
- package/distilled/workflows/resume.md +4 -0
- package/distilled/workflows/verify.md +2 -1
- package/docs/USER-GUIDE.md +3 -1
- package/package.json +2 -2
package/bin/lib/init-flow.mjs
CHANGED
|
@@ -100,6 +100,7 @@ export function createCmdInit(ctx) {
|
|
|
100
100
|
promptApi,
|
|
101
101
|
preselectedConfig: interactiveSession.config,
|
|
102
102
|
});
|
|
103
|
+
ensureGitignoreEntry(ctx.cwd, '.planning/.local/', ' - ensured .planning/.local/ is gitignored');
|
|
103
104
|
|
|
104
105
|
if (briefSource) {
|
|
105
106
|
cpSync(briefSource, join(ctx.planningDir, 'PROJECT_BRIEF.md'));
|
|
@@ -273,7 +274,7 @@ async function ensureConfig({ cwd, planningDir, isAuto, promptApi, preselectedCo
|
|
|
273
274
|
if (preselectedConfig) {
|
|
274
275
|
writeFileSync(configFile, JSON.stringify(preselectedConfig, null, 2));
|
|
275
276
|
console.log(' - saved .planning/config.json (guided wizard)\n');
|
|
276
|
-
if (!preselectedConfig.commitDocs) ensureGitignoreEntry(cwd);
|
|
277
|
+
if (!preselectedConfig.commitDocs) ensureGitignoreEntry(cwd, '.planning/', ' - ensured .planning/ is gitignored');
|
|
277
278
|
return;
|
|
278
279
|
}
|
|
279
280
|
|
|
@@ -281,7 +282,7 @@ async function ensureConfig({ cwd, planningDir, isAuto, promptApi, preselectedCo
|
|
|
281
282
|
const config = buildDefaultConfig({ autoAdvance: true });
|
|
282
283
|
writeFileSync(configFile, JSON.stringify(config, null, 2));
|
|
283
284
|
console.log(' - wrote .planning/config.json (auto defaults)\n');
|
|
284
|
-
if (!config.commitDocs) ensureGitignoreEntry(cwd);
|
|
285
|
+
if (!config.commitDocs) ensureGitignoreEntry(cwd, '.planning/', ' - ensured .planning/ is gitignored');
|
|
285
286
|
return;
|
|
286
287
|
}
|
|
287
288
|
|
|
@@ -289,7 +290,7 @@ async function ensureConfig({ cwd, planningDir, isAuto, promptApi, preselectedCo
|
|
|
289
290
|
const config = buildDefaultConfig({ autoAdvance: false });
|
|
290
291
|
writeFileSync(configFile, JSON.stringify(config, null, 2));
|
|
291
292
|
console.log(' - wrote .planning/config.json (non-interactive defaults)\n');
|
|
292
|
-
if (!config.commitDocs) ensureGitignoreEntry(cwd);
|
|
293
|
+
if (!config.commitDocs) ensureGitignoreEntry(cwd, '.planning/', ' - ensured .planning/ is gitignored');
|
|
293
294
|
return;
|
|
294
295
|
}
|
|
295
296
|
|
|
@@ -303,18 +304,17 @@ async function ensureConfig({ cwd, planningDir, isAuto, promptApi, preselectedCo
|
|
|
303
304
|
|
|
304
305
|
writeFileSync(configFile, JSON.stringify(selected, null, 2));
|
|
305
306
|
console.log(' - saved .planning/config.json (guided wizard)\n');
|
|
306
|
-
if (!selected.commitDocs) ensureGitignoreEntry(cwd);
|
|
307
|
+
if (!selected.commitDocs) ensureGitignoreEntry(cwd, '.planning/', ' - ensured .planning/ is gitignored');
|
|
307
308
|
}
|
|
308
309
|
|
|
309
|
-
function ensureGitignoreEntry(cwd) {
|
|
310
|
+
function ensureGitignoreEntry(cwd, entry, message) {
|
|
310
311
|
const gitignorePath = join(cwd, '.gitignore');
|
|
311
|
-
const entry = '.planning/';
|
|
312
312
|
const hasGitignore = existsSync(gitignorePath);
|
|
313
313
|
const current = hasGitignore ? readFileSync(gitignorePath, 'utf-8') : '';
|
|
314
314
|
if (!current.split(/\r?\n/).includes(entry)) {
|
|
315
315
|
const next = current.trimEnd() ? `${current.trimEnd()}\n${entry}\n` : `${entry}\n`;
|
|
316
316
|
writeFileSync(gitignorePath, next);
|
|
317
|
-
console.log(
|
|
317
|
+
console.log(message);
|
|
318
318
|
}
|
|
319
319
|
}
|
|
320
320
|
|
package/bin/lib/init-runtime.mjs
CHANGED
|
@@ -192,6 +192,8 @@ Commands:
|
|
|
192
192
|
Validate UI proof metadata; use --claim for stronger proof uses
|
|
193
193
|
ui-proof compare <planned-slots-json> [observed-bundle-json ...]
|
|
194
194
|
Compare planned UI proof slots against observed bundles
|
|
195
|
+
control-map [--json] [--with-ignored] [--annotations <path>]
|
|
196
|
+
Report computed repo/worktree/planning state and local annotations
|
|
195
197
|
help Show this summary
|
|
196
198
|
|
|
197
199
|
Platforms (for --tools):
|
|
@@ -263,6 +265,7 @@ Advanced/internal helpers (kept available, but not the primary first-run user st
|
|
|
263
265
|
session-fingerprint Rebaseline the local planning-state fingerprint after review
|
|
264
266
|
phase-status Update ROADMAP.md phase status through the local helper surface
|
|
265
267
|
ui-proof Validate UI proof metadata and compare planned slots to observed bundles
|
|
268
|
+
control-map Report computed repo/worktree/planning state and local annotations
|
|
266
269
|
file-op Deterministic workspace-confined file copy/delete/text mutation
|
|
267
270
|
`;
|
|
268
271
|
}
|
package/bin/lib/phase.mjs
CHANGED
|
@@ -4,10 +4,16 @@
|
|
|
4
4
|
// evaluate once, so CWD must be computed inside function bodies.
|
|
5
5
|
|
|
6
6
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from 'fs';
|
|
7
|
-
import { join,
|
|
7
|
+
import { dirname, join, relative } from 'path';
|
|
8
8
|
import { output } from './cli-utils.mjs';
|
|
9
9
|
import { writeFingerprint } from './session-fingerprint.mjs';
|
|
10
10
|
import { resolveWorkspaceContext } from './workspace-root.mjs';
|
|
11
|
+
import {
|
|
12
|
+
compareUiProofSlots,
|
|
13
|
+
findUiProofBundleFiles,
|
|
14
|
+
parseUiProofSlotsContent,
|
|
15
|
+
readUiProofBundleFile,
|
|
16
|
+
} from './ui-proof.mjs';
|
|
11
17
|
|
|
12
18
|
const PHASE_STATUS_MARKERS = {
|
|
13
19
|
not_started: '[ ]',
|
|
@@ -169,6 +175,200 @@ function extractPlanFileArtifacts(planContent, workspaceRoot) {
|
|
|
169
175
|
return artifacts;
|
|
170
176
|
}
|
|
171
177
|
|
|
178
|
+
function isPlanArtifactSatisfied(artifact) {
|
|
179
|
+
if (artifact.operation === 'delete') return !artifact.exists;
|
|
180
|
+
return artifact.exists;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function planArtifactFixHint(artifact) {
|
|
184
|
+
if (artifact.operation === 'delete') {
|
|
185
|
+
return `Complete the planned DELETE for ${artifact.file}, or revise the plan if the file should remain.`;
|
|
186
|
+
}
|
|
187
|
+
return `Create or update ${artifact.file} so the planned ${artifact.operation.toUpperCase()} artifact exists, or revise the plan if it is no longer in scope.`;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function evaluatePlanArtifacts(artifacts) {
|
|
191
|
+
const unsatisfied = artifacts
|
|
192
|
+
.filter((artifact) => !isPlanArtifactSatisfied(artifact))
|
|
193
|
+
.map((artifact) => ({
|
|
194
|
+
...artifact,
|
|
195
|
+
severity: 'blocker',
|
|
196
|
+
expected: artifact.operation === 'delete' ? 'absent' : 'present',
|
|
197
|
+
fix_hint: planArtifactFixHint(artifact),
|
|
198
|
+
}));
|
|
199
|
+
return {
|
|
200
|
+
satisfied: unsatisfied.length === 0,
|
|
201
|
+
unsatisfied,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function normalizeUiProofIssue(issue) {
|
|
206
|
+
return {
|
|
207
|
+
...issue,
|
|
208
|
+
severity: issue.severity || 'blocker',
|
|
209
|
+
fix_hint: issue.fix_hint || issue.fix || 'Fix the UI proof issue before claiming verification is complete.',
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function planDeclaresUiProofSlots(planContent) {
|
|
214
|
+
const match = String(planContent || '').match(/(^|\n)ui_proof_slots:[ \t]*([^\n]*)/);
|
|
215
|
+
if (!match) return false;
|
|
216
|
+
const inlineValue = match[2].replace(/\s+#.*$/, '').trim();
|
|
217
|
+
if (inlineValue) return !['[]', 'null', '~'].includes(inlineValue);
|
|
218
|
+
const after = String(planContent || '').slice(match.index + match[0].length).split(/\r?\n/);
|
|
219
|
+
for (const line of after) {
|
|
220
|
+
const trimmed = line.trim();
|
|
221
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
222
|
+
if (trimmed === '---' || trimmed === '...') break;
|
|
223
|
+
if (/^\s+-\s+/.test(line)) return true;
|
|
224
|
+
if (/^\S[^:\n]*:\s*/.test(line) || /^\S/.test(line)) break;
|
|
225
|
+
}
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function extractDeclaredUiProofSlotIds(planContent) {
|
|
230
|
+
const match = String(planContent || '').match(/(^|\n)ui_proof_slots:[ \t]*([^\n]*)/);
|
|
231
|
+
if (!match) return [];
|
|
232
|
+
const after = String(planContent || '').slice(match.index + match[0].length).split(/\r?\n/);
|
|
233
|
+
const slotIds = [];
|
|
234
|
+
for (const line of after) {
|
|
235
|
+
const trimmed = line.trim();
|
|
236
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
237
|
+
if (trimmed === '---' || trimmed === '...') break;
|
|
238
|
+
if (/^\S[^:\n]*:\s*/.test(line) || /^\S/.test(line)) break;
|
|
239
|
+
const slotMatch = trimmed.match(/(?:^-\s*)?slot_id:\s*([^#\s]+)/);
|
|
240
|
+
if (slotMatch) slotIds.push(slotMatch[1].replace(/^['"]|['"]$/g, ''));
|
|
241
|
+
}
|
|
242
|
+
return slotIds;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function findUiProofSlotPlansAndFiles(planningDir, planDisplayPaths) {
|
|
246
|
+
const candidates = new Set();
|
|
247
|
+
const declaredPlans = [];
|
|
248
|
+
const declaredSlotIds = [];
|
|
249
|
+
const names = new Set([
|
|
250
|
+
'ui-proof-slots.json',
|
|
251
|
+
'ui-proof-slots.md',
|
|
252
|
+
'UI-PROOF-SLOTS.json',
|
|
253
|
+
'UI-PROOF-SLOTS.md',
|
|
254
|
+
'planned-ui-proof.json',
|
|
255
|
+
'planned-ui-proof.md',
|
|
256
|
+
]);
|
|
257
|
+
|
|
258
|
+
for (const planDisplayPath of planDisplayPaths) {
|
|
259
|
+
const fullPlanPath = join(planningDir, 'phases', planDisplayPath);
|
|
260
|
+
if (!existsSync(fullPlanPath)) continue;
|
|
261
|
+
const planContent = readFileSync(fullPlanPath, 'utf-8');
|
|
262
|
+
if (!planDeclaresUiProofSlots(planContent)) continue;
|
|
263
|
+
const relPlanPath = relative(planningDir, fullPlanPath).replace(/\\/g, '/');
|
|
264
|
+
declaredPlans.push(relPlanPath);
|
|
265
|
+
for (const slotId of extractDeclaredUiProofSlotIds(planContent)) {
|
|
266
|
+
declaredSlotIds.push({ plan: relPlanPath, slot_id: slotId });
|
|
267
|
+
}
|
|
268
|
+
const planDir = dirname(fullPlanPath);
|
|
269
|
+
if (!existsSync(planDir)) continue;
|
|
270
|
+
for (const entry of readdirSync(planDir, { withFileTypes: true })) {
|
|
271
|
+
if (entry.isFile() && names.has(entry.name)) {
|
|
272
|
+
candidates.add(join(planDir, entry.name));
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return { declaredPlans, declaredSlotIds, files: [...candidates].sort() };
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function comparePhaseUiProof({ planningDir, workspaceRoot, planDisplayPaths }) {
|
|
280
|
+
const plannedDiscovery = findUiProofSlotPlansAndFiles(planningDir, planDisplayPaths);
|
|
281
|
+
const plannedFiles = plannedDiscovery.files;
|
|
282
|
+
const phaseDirs = new Set(planDisplayPaths.map((planDisplayPath) => dirname(join(planningDir, 'phases', planDisplayPath))));
|
|
283
|
+
const observedFiles = findUiProofBundleFiles(planningDir)
|
|
284
|
+
.filter((filePath) => phaseDirs.has(dirname(filePath)));
|
|
285
|
+
|
|
286
|
+
const plannedSlots = [];
|
|
287
|
+
const errors = [];
|
|
288
|
+
const planned = [];
|
|
289
|
+
const observed = [];
|
|
290
|
+
|
|
291
|
+
for (const filePath of plannedFiles) {
|
|
292
|
+
const rel = relative(workspaceRoot, filePath).replace(/\\/g, '/');
|
|
293
|
+
const parsed = parseUiProofSlotsContent(readFileSync(filePath, 'utf-8'), rel);
|
|
294
|
+
planned.push(rel);
|
|
295
|
+
plannedSlots.push(...parsed.slots);
|
|
296
|
+
errors.push(...parsed.errors.map(normalizeUiProofIssue));
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (plannedSlots.length > 0 && plannedDiscovery.declaredSlotIds.length > 0) {
|
|
300
|
+
const plannedSlotIds = new Set(plannedSlots.map((slot) => String(slot?.slot_id || '')));
|
|
301
|
+
for (const declaredSlot of plannedDiscovery.declaredSlotIds) {
|
|
302
|
+
if (plannedSlotIds.has(String(declaredSlot.slot_id))) continue;
|
|
303
|
+
errors.push(normalizeUiProofIssue({
|
|
304
|
+
code: 'planned_ui_proof_slots_drift',
|
|
305
|
+
path: `${declaredSlot.plan}.ui_proof_slots`,
|
|
306
|
+
message: `Plan declares UI proof slot ${declaredSlot.slot_id}, but no matching slot exists in the planned UI proof artifact.`,
|
|
307
|
+
fix: 'Update ui-proof-slots.json or ui-proof-slots.md beside the plan so it matches the plan-declared slot IDs, or update the plan declaration.',
|
|
308
|
+
}));
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const observedBundles = [];
|
|
313
|
+
for (const filePath of observedFiles) {
|
|
314
|
+
const rel = relative(workspaceRoot, filePath).replace(/\\/g, '/');
|
|
315
|
+
const parsed = readUiProofBundleFile(filePath);
|
|
316
|
+
observed.push(rel);
|
|
317
|
+
if (parsed.errors.length > 0) {
|
|
318
|
+
errors.push(...parsed.errors.map((error) => normalizeUiProofIssue({ ...error, path: error.path || rel })));
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
observedBundles.push({
|
|
322
|
+
source: rel,
|
|
323
|
+
bundle: parsed.bundle,
|
|
324
|
+
options: {
|
|
325
|
+
requireLocalArtifactExists: true,
|
|
326
|
+
workspaceRoot,
|
|
327
|
+
bundleDir: dirname(filePath),
|
|
328
|
+
},
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (plannedFiles.length === 0 && plannedDiscovery.declaredPlans.length > 0) {
|
|
333
|
+
const missingError = {
|
|
334
|
+
code: 'missing_planned_ui_proof_slots_file',
|
|
335
|
+
severity: 'blocker',
|
|
336
|
+
path: plannedDiscovery.declaredPlans[0],
|
|
337
|
+
message: 'Plan declares ui_proof_slots but no ui-proof-slots artifact was found beside the plan.',
|
|
338
|
+
fix_hint: 'Create ui-proof-slots.json or ui-proof-slots.md beside the plan, or set ui_proof_slots: [] with a no_ui_proof_rationale if the phase is not UI-sensitive.',
|
|
339
|
+
};
|
|
340
|
+
return {
|
|
341
|
+
planned,
|
|
342
|
+
observed,
|
|
343
|
+
status: 'missing',
|
|
344
|
+
comparison: { status: 'missing', slots: [], errors: [missingError] },
|
|
345
|
+
errors: [missingError],
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (plannedFiles.length === 0) {
|
|
350
|
+
return {
|
|
351
|
+
planned,
|
|
352
|
+
observed,
|
|
353
|
+
status: 'not_applicable',
|
|
354
|
+
comparison: null,
|
|
355
|
+
errors,
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const comparison = errors.length > 0
|
|
360
|
+
? { status: 'partial', slots: [], errors: errors.map(normalizeUiProofIssue) }
|
|
361
|
+
: compareUiProofSlots(plannedSlots, observedBundles);
|
|
362
|
+
|
|
363
|
+
return {
|
|
364
|
+
planned,
|
|
365
|
+
observed,
|
|
366
|
+
status: comparison.status,
|
|
367
|
+
comparison,
|
|
368
|
+
errors: comparison.errors || errors,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
172
372
|
export function updateRoadmapPhaseStatus(roadmap, phaseNumber, status) {
|
|
173
373
|
const marker = PHASE_STATUS_MARKERS[status];
|
|
174
374
|
if (!marker) {
|
|
@@ -360,6 +560,26 @@ export function cmdVerify(...args) {
|
|
|
360
560
|
? extractPlanFileArtifacts(readFileSync(fullPath, 'utf-8'), workspaceRoot)
|
|
361
561
|
: [];
|
|
362
562
|
});
|
|
563
|
+
const artifactStatus = evaluatePlanArtifacts(artifacts);
|
|
564
|
+
const uiProof = comparePhaseUiProof({
|
|
565
|
+
planningDir,
|
|
566
|
+
workspaceRoot,
|
|
567
|
+
planDisplayPaths: matchingPlans,
|
|
568
|
+
});
|
|
569
|
+
const uiProofSatisfied = ['satisfied', 'not_applicable'].includes(uiProof.status);
|
|
570
|
+
const legacyVerified = matchingPlans.length > 0 && matchingSummaries.length > 0;
|
|
571
|
+
const uiProofGate = {
|
|
572
|
+
status: uiProof.status,
|
|
573
|
+
required: uiProof.status !== 'not_applicable',
|
|
574
|
+
satisfied: uiProofSatisfied,
|
|
575
|
+
blocks_verification: uiProof.status !== 'not_applicable' && !uiProofSatisfied,
|
|
576
|
+
required_block: uiProof.status !== 'not_applicable' && !uiProofSatisfied ? 'ui-proof-failed' : null,
|
|
577
|
+
};
|
|
578
|
+
const blockedOn = [
|
|
579
|
+
...(artifactStatus.satisfied ? [] : ['artifacts']),
|
|
580
|
+
...(uiProofGate.blocks_verification ? ['ui_proof'] : []),
|
|
581
|
+
];
|
|
582
|
+
const closureVerified = legacyVerified && artifactStatus.satisfied && uiProofSatisfied;
|
|
363
583
|
|
|
364
584
|
const result = {
|
|
365
585
|
phase: normalizePhaseToken(phaseNum),
|
|
@@ -368,9 +588,17 @@ export function cmdVerify(...args) {
|
|
|
368
588
|
summaries: matchingSummaries,
|
|
369
589
|
artifacts,
|
|
370
590
|
allExist: artifacts.every((artifact) => artifact.exists),
|
|
371
|
-
|
|
591
|
+
artifact_status: artifactStatus,
|
|
592
|
+
uiProof,
|
|
593
|
+
verified: closureVerified,
|
|
594
|
+
legacy_verified: legacyVerified,
|
|
595
|
+
phase_artifacts_present: legacyVerified,
|
|
596
|
+
ui_proof: uiProofGate,
|
|
597
|
+
blocked_on: blockedOn,
|
|
598
|
+
blocks_verification: blockedOn.length > 0,
|
|
372
599
|
};
|
|
373
600
|
output(result);
|
|
601
|
+
if (!closureVerified && legacyVerified) process.exitCode = 1;
|
|
374
602
|
}
|
|
375
603
|
|
|
376
604
|
export function cmdScaffold(...args) {
|
package/bin/lib/rendering.mjs
CHANGED
|
@@ -7,6 +7,7 @@ const __dirname = dirname(__filename);
|
|
|
7
7
|
const DISTILLED_DIR = join(__dirname, '..', '..', 'distilled');
|
|
8
8
|
const HELPER_LIB_FILES = Object.freeze([
|
|
9
9
|
'cli-utils.mjs',
|
|
10
|
+
'control-map.mjs',
|
|
10
11
|
'evidence-contract.mjs',
|
|
11
12
|
'file-ops.mjs',
|
|
12
13
|
'lifecycle-preflight.mjs',
|
|
@@ -49,6 +50,7 @@ import { cmdLifecyclePreflight } from './lib/lifecycle-preflight.mjs';
|
|
|
49
50
|
import { cmdPhaseStatus } from './lib/phase.mjs';
|
|
50
51
|
import { cmdSessionFingerprint } from './lib/session-fingerprint.mjs';
|
|
51
52
|
import { cmdUiProof } from './lib/ui-proof.mjs';
|
|
53
|
+
import { cmdControlMap } from './lib/control-map.mjs';
|
|
52
54
|
import { bootstrapHelperWorkspace, consumeWorkspaceRootArg, resolveWorkspaceContext } from './lib/workspace-root.mjs';
|
|
53
55
|
|
|
54
56
|
const COMMANDS = {
|
|
@@ -57,6 +59,7 @@ const COMMANDS = {
|
|
|
57
59
|
'phase-status': cmdPhaseStatus,
|
|
58
60
|
'session-fingerprint': cmdSessionFingerprint,
|
|
59
61
|
'ui-proof': cmdUiProof,
|
|
62
|
+
'control-map': cmdControlMap,
|
|
60
63
|
};
|
|
61
64
|
|
|
62
65
|
function printHelp() {
|
|
@@ -78,6 +81,8 @@ function printHelp() {
|
|
|
78
81
|
' Validate UI proof metadata; use --claim for stronger proof uses',
|
|
79
82
|
' ui-proof compare <planned-slots-json> [observed-bundle-json ...]',
|
|
80
83
|
' Compare planned UI proof slots against observed bundles',
|
|
84
|
+
' control-map [--json] [--with-ignored] [--annotations <path>]',
|
|
85
|
+
' Report computed repo/worktree/planning state and local annotations',
|
|
81
86
|
'',
|
|
82
87
|
'Advanced option:',
|
|
83
88
|
' --workspace-root <path> Override workspace root discovery before or after the subcommand',
|
package/bin/lib/ui-proof.mjs
CHANGED
|
@@ -446,6 +446,41 @@ function normalizeObservedBundle(entry) {
|
|
|
446
446
|
};
|
|
447
447
|
}
|
|
448
448
|
|
|
449
|
+
function comparisonFixHint(code) {
|
|
450
|
+
const hints = {
|
|
451
|
+
invalid_observed_bundle: 'Fix the observed proof bundle metadata, then rerun ui-proof compare.',
|
|
452
|
+
unsatisfied_observed_claim_status: 'Record a passed observed claim only after the changed UI state has been exercised and evidenced.',
|
|
453
|
+
unsatisfied_observed_comparison_status: 'Set comparison_status_by_slot to satisfied only for slots backed by matching observations and artifacts.',
|
|
454
|
+
missing_required_evidence_kind: 'Add observed evidence for every evidence kind required by the planned slot, or narrow the planned slot before verification.',
|
|
455
|
+
human_evidence_cannot_bypass_required_non_human_evidence: 'Add the missing non-human evidence; human approval may narrow or waive but cannot replace it.',
|
|
456
|
+
route_state_mismatch: 'Capture proof for the exact planned route/state, or update the plan before execution.',
|
|
457
|
+
environment_mismatch: 'Capture proof in the planned environment, or record a narrowed claim limit and rerun comparison.',
|
|
458
|
+
viewport_mismatch: 'Capture proof for the planned viewport, or narrow the viewport claim explicitly.',
|
|
459
|
+
requirement_mismatch: 'Declare the planned requirement id in the observed proof bundle scope.',
|
|
460
|
+
claim_mismatch: 'Keep the planned and observed claims identical so proof maps to the exact UI assertion.',
|
|
461
|
+
observation_claim_mismatch: 'Add a passed observation that supports the exact planned claim.',
|
|
462
|
+
observation_route_state_mismatch: 'Attach observations to the exact planned route/state.',
|
|
463
|
+
missing_supporting_observation_evidence_kind: 'Add passed supporting observations for each required evidence kind.',
|
|
464
|
+
unsatisfied_proof_step: 'Rerun or replace failing proof steps before claiming the slot is satisfied.',
|
|
465
|
+
missing_manual_acceptance_evidence: 'Record human evidence when the planned slot requires manual acceptance.',
|
|
466
|
+
missing_manual_acceptance_observation: 'Add a passed human observation for manual acceptance.',
|
|
467
|
+
unsatisfied_observation_result: 'Resolve failed observations or classify the slot as partial, waived, or deferred.',
|
|
468
|
+
missing_minimum_observation: 'Add observations covering every planned minimum observation.',
|
|
469
|
+
missing_claim_limit: 'Preserve the planned claim limit in the observed proof bundle.',
|
|
470
|
+
missing_expected_artifact_type: 'Attach the planned artifact type, such as screenshot, report, trace, or DOM snapshot.',
|
|
471
|
+
missing_observed_bundle: 'Create an observed UI proof bundle for the planned slot, or explicitly waive/defer the slot with claim narrowing.',
|
|
472
|
+
};
|
|
473
|
+
return hints[code] || 'Fix the proof issue, rerun the comparison, and keep the slot partial until evidence matches the plan.';
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function decorateComparisonIssue(issue) {
|
|
477
|
+
return {
|
|
478
|
+
severity: issue.severity || 'blocker',
|
|
479
|
+
fix_hint: issue.fix_hint || issue.fix || comparisonFixHint(issue.code),
|
|
480
|
+
...issue,
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
|
|
449
484
|
function compareSlotToBundle(slot, slotIdValue, observed) {
|
|
450
485
|
const issues = [];
|
|
451
486
|
const bundle = observed.bundle;
|
|
@@ -648,7 +683,7 @@ function compareSlotToBundle(slot, slotIdValue, observed) {
|
|
|
648
683
|
}
|
|
649
684
|
|
|
650
685
|
const status = issues.length === 0 ? 'satisfied' : (bundleStatus === 'missing' ? 'missing' : 'partial');
|
|
651
|
-
return { status, issues, source: observed.source };
|
|
686
|
+
return { status, issues: issues.map(decorateComparisonIssue), source: observed.source };
|
|
652
687
|
}
|
|
653
688
|
|
|
654
689
|
export function compareUiProofSlots(plannedSlots, observedBundles) {
|
|
@@ -656,7 +691,7 @@ export function compareUiProofSlots(plannedSlots, observedBundles) {
|
|
|
656
691
|
const slotValidation = validateUiProofSlots(slots);
|
|
657
692
|
const bundles = normalizeArray(observedBundles).map(normalizeObservedBundle);
|
|
658
693
|
const results = [];
|
|
659
|
-
const errors =
|
|
694
|
+
const errors = slotValidation.errors.map(decorateComparisonIssue);
|
|
660
695
|
|
|
661
696
|
for (const observed of bundles) {
|
|
662
697
|
if (!observed.validation.valid) {
|
|
@@ -680,7 +715,7 @@ export function compareUiProofSlots(plannedSlots, observedBundles) {
|
|
|
680
715
|
code: 'missing_observed_bundle',
|
|
681
716
|
path: 'scope.slot_ids',
|
|
682
717
|
message: `No observed UI proof bundle declares planned slot ${slotIdValue}.`,
|
|
683
|
-
}],
|
|
718
|
+
}].map(decorateComparisonIssue),
|
|
684
719
|
});
|
|
685
720
|
continue;
|
|
686
721
|
}
|
|
@@ -706,7 +741,7 @@ export function compareUiProofSlots(plannedSlots, observedBundles) {
|
|
|
706
741
|
? 'missing'
|
|
707
742
|
: 'partial';
|
|
708
743
|
|
|
709
|
-
return { status, slots: results, errors };
|
|
744
|
+
return { status, slots: results, errors: errors.map(decorateComparisonIssue) };
|
|
710
745
|
}
|
|
711
746
|
|
|
712
747
|
export function validateUiProofBundle(bundle, options = {}) {
|
package/distilled/DESIGN.md
CHANGED
|
@@ -73,6 +73,7 @@
|
|
|
73
73
|
60. [Release Closeout Contract](#d60---release-closeout-contract)
|
|
74
74
|
61. [Deliberate Subagent Contract](#d61---deliberate-subagent-contract)
|
|
75
75
|
62. [Repo-Native UI Proof Contract](#d62---repo-native-ui-proof-contract)
|
|
76
|
+
63. [Computed-First Control Map](#d63---computed-first-control-map)
|
|
76
77
|
|
|
77
78
|
---
|
|
78
79
|
|
|
@@ -2872,6 +2873,44 @@ Posture compatibility is part of that closeout contract: `repo_closeout` and `ru
|
|
|
2872
2873
|
- Generated runtime surfaces and local templates must stay freshness-checkable through `gsdd update --templates` and health diagnostics.
|
|
2873
2874
|
- Future provider/tooling work must not make `agent-browser` a required validator field without a separate product decision; the current contract makes it the default workflow path, not a schema lock.
|
|
2874
2875
|
|
|
2876
|
+
## D63 - Computed-First Control Map
|
|
2877
|
+
|
|
2878
|
+
**Decision (2026-05-08):** Long-running multi-agent and multi-worktree control uses a computed-first `gsdd control-map` helper rather than a new lifecycle workflow or a vendor session parser. The helper computes repo/worktree/planning truth live and overlays optional local annotations only for intent that git cannot know.
|
|
2879
|
+
|
|
2880
|
+
**Context:**
|
|
2881
|
+
- Gap I52 showed that ordinary `git status` can be clean while sibling worktrees, detached runtime worktrees, ignored/generated surfaces, snapshots, dirty local WIP, and cleanup obligations remain unexplained.
|
|
2882
|
+
- Gap I54 showed that repeated subagent swarms can become a substitute for shared state when there is no one-screen control map for active branches, ownership, proof state, and cleanup debt.
|
|
2883
|
+
- Current runtime research shows the only portable cross-vendor coordination layer is repo artifacts plus generated workflow entrypoints. Claude, OpenCode, Codex, Cursor, Copilot, and Gemini do not expose one uniform authoritative session/worktree store.
|
|
2884
|
+
- Current harness guidance favors structured handoff artifacts, worktree isolation, evaluator loops, approval gates around side effects, and browser/runtime evidence. Those ideas fit Workspine only if repo truth remains primary and vendor adapters stay thin.
|
|
2885
|
+
|
|
2886
|
+
**Decision:**
|
|
2887
|
+
- Add `gsdd control-map [--json] [--with-ignored] [--annotations <path>]` to the main CLI and generated `.planning/bin/gsdd.mjs` helper runtime.
|
|
2888
|
+
- Compute authority from live git/worktree state first: canonical checkout, branch, HEAD, upstream divergence when comparable, tracked/untracked dirty buckets, optional ignored-path scans through `--with-ignored`, sibling git worktrees, detached/bare state, invalid git access, planning drift, checkpoint existence, lifecycle state, and repo-local runtime worktree directories.
|
|
2889
|
+
- Read optional annotations from `.planning/.local/control-map.annotations.json`. Annotations may record `runtime_owner`, intended scope, write set, cleanup state, proof state, next step, branch, and last known head.
|
|
2890
|
+
- Treat annotations as stale-checkable intent only. They never outrank repo truth, planning artifacts, or checkpoint reconciliation.
|
|
2891
|
+
- Keep transcript/session stores out of the helper. Vendor session evidence may support postmortems, but it is not live product truth.
|
|
2892
|
+
- Wire the control map into portable workflow behavior by having `progress`, `resume`, `pause`, `quick`, `plan`, and `execute` consult it when available. This is guidance plus deterministic helper output, not a new workflow lane.
|
|
2893
|
+
|
|
2894
|
+
**Leverage:**
|
|
2895
|
+
- Lost: a pure zero-file model cannot preserve non-computable intent such as owner/runtime, intended scope, and cleanup obligation.
|
|
2896
|
+
- Kept: Workspine remains a lightweight repo-native spine; no new lifecycle workflow, no dashboard/control plane, no vendor session authority, and no change to the five evidence kinds.
|
|
2897
|
+
- Gained: agents can explain "clean" precisely across tracked, untracked, sibling, detached, stale, and annotated state by default, and across ignored/generated local surfaces when the caller requests the explicit `--with-ignored` scan before planning, execution, resume, cleanup, or milestone continuation.
|
|
2898
|
+
|
|
2899
|
+
**Evidence:**
|
|
2900
|
+
- `bin/lib/control-map.mjs`, `bin/gsdd.mjs`, `bin/lib/rendering.mjs`
|
|
2901
|
+
- `distilled/workflows/progress.md`, `resume.md`, `pause.md`, `quick.md`, `plan.md`, `execute.md`
|
|
2902
|
+
- `tests/gsdd.control-map.test.cjs`
|
|
2903
|
+
- `.internal-research/gaps.md` Gap I52 and Gap I54
|
|
2904
|
+
- `.internal-research/lessons-learned.md` entries on multi-worktree registry, clean-vs-editor-visible noise, checkpoint/worktree truth split, and subagent stop conditions
|
|
2905
|
+
- GSD comparison: upstream GSD preserves lifecycle rigor but does not define a vendor-agnostic computed worktree/control-map helper.
|
|
2906
|
+
- OpenSpec comparison: OpenSpec optimizes change-level speed and archive flow, but does not own long-running multi-worktree local-state reconciliation as a portable harness surface.
|
|
2907
|
+
- Harness sources: `https://www.anthropic.com/engineering/harness-design-long-running-apps`, `https://code.claude.com/docs/en/worktrees`, `https://developers.openai.com/codex/cloud`, `https://developers.openai.com/api/docs/guides/agents/orchestration`, `https://developers.openai.com/api/docs/guides/agents/guardrails-approvals`, `https://developers.openai.com/api/docs/guides/agent-evals`, `https://docs.github.com/en/copilot/concepts/agents/cloud-agent/about-cloud-agent`, `https://agent-browser.dev/sessions`, and `https://developer.chrome.com/docs/devtools/agents`.
|
|
2908
|
+
|
|
2909
|
+
**Consequences:**
|
|
2910
|
+
- Future cleanup, resume, and parallel-worktree work should start from `gsdd control-map --json` rather than repeated ad hoc repo audits; use `--with-ignored` before making a clean-workspace claim that includes ignored or generated surfaces.
|
|
2911
|
+
- A future mutation command may update annotations, but the current helper intentionally stays computed/read-first and safe to call from status surfaces.
|
|
2912
|
+
- Future health or preflight hardening can consume the same helper output for stricter blocking, but must avoid turning local annotations into product truth.
|
|
2913
|
+
|
|
2875
2914
|
---
|
|
2876
2915
|
|
|
2877
2916
|
## Maintenance
|
|
@@ -505,6 +505,16 @@
|
|
|
505
505
|
- Long-term pitfalls carried forward: do not accept screenshot-free "looks good" claims, weak planned slots, unverified artifact paths, stale interactive refs after page mutation, partial/failed proof without failure classification, raw artifact publication without privacy metadata, browser contention in parallel checks, or semantic/selector-poor UI that forces fragile coordinate inspection.
|
|
506
506
|
- Supporting spec/runtime docs: https://openspec.dev/, https://www.lean-spec.dev/docs/guide/first-principles, https://help.openai.com/en/articles/11369540-codex-in-chatgpt, https://docs.claude.com/en/docs/agents-and-tools/agent-skills, https://docs.github.com/en/copilot/concepts/prompting/response-customization
|
|
507
507
|
|
|
508
|
+
## D63 — Computed-First Control Map
|
|
509
|
+
- `bin/lib/control-map.mjs`, `bin/gsdd.mjs`, `bin/lib/rendering.mjs`
|
|
510
|
+
- `distilled/workflows/progress.md`, `resume.md`, `pause.md`, `quick.md`, `plan.md`, `execute.md`
|
|
511
|
+
- `tests/gsdd.control-map.test.cjs`
|
|
512
|
+
- `.internal-research/gaps.md` Gap I52 and Gap I54
|
|
513
|
+
- `.internal-research/lessons-learned.md` multi-worktree registry, clean-vs-editor-visible noise, checkpoint/worktree truth split, and subagent stop-condition lessons
|
|
514
|
+
- GSD comparison: upstream GSD lifecycle rigor does not include a vendor-agnostic computed worktree/control-map helper
|
|
515
|
+
- OpenSpec comparison: OpenSpec change archive flow does not own long-running multi-worktree local-state reconciliation as a portable harness surface
|
|
516
|
+
- Harness sources: https://www.anthropic.com/engineering/harness-design-long-running-apps, https://code.claude.com/docs/en/worktrees, https://developers.openai.com/codex/cloud, https://developers.openai.com/api/docs/guides/agents/orchestration, https://developers.openai.com/api/docs/guides/agents/guardrails-approvals, https://developers.openai.com/api/docs/guides/agent-evals, https://docs.github.com/en/copilot/concepts/agents/cloud-agent/about-cloud-agent, https://agent-browser.dev/sessions, https://developer.chrome.com/docs/devtools/agents
|
|
517
|
+
|
|
508
518
|
---
|
|
509
519
|
|
|
510
520
|
## Maintenance
|
package/distilled/README.md
CHANGED
|
@@ -71,6 +71,14 @@ npx -y gsdd-cli init -> bootstrap (create .planning/, copy templates, gene
|
|
|
71
71
|
/gsdd-progress -> show status, route to next action
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
+
The main operator spine is four workflow moves after bootstrap: `new-project -> plan -> execute -> verify`. The other public workflow surfaces are support lanes for milestone closeout, quick work, progress, pause/resume, and brownfield orientation.
|
|
75
|
+
|
|
76
|
+
Helper command for long-running sessions:
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
npx -y gsdd-cli control-map [--json] [--with-ignored] -> computed repo/worktree/planning state plus local annotations
|
|
80
|
+
```
|
|
81
|
+
|
|
74
82
|
## Brownfield Entry Contract
|
|
75
83
|
|
|
76
84
|
Use the same three-way routing everywhere:
|
|
@@ -101,6 +109,7 @@ Use the same three-way routing everywhere:
|
|
|
101
109
|
Architecture notes:
|
|
102
110
|
- `bin/gsdd.mjs` remains the thin generator entrypoint, while vendor-specific rendering lives in adapter modules.
|
|
103
111
|
- Codex CLI uses the always-generated `.agents/skills/gsdd-*` surface as its entry path, relies on `.planning/bin/gsdd.mjs` for deterministic helper calls, and can add a native `.codex/agents/gsdd-plan-checker.toml` checker agent.
|
|
112
|
+
- `control-map` is a helper command, not a lifecycle workflow: it computes repo/worktree/planning truth first and treats `.planning/.local/` annotations as local intent only.
|
|
104
113
|
- Codex VS Code/app are separate surfaces from Codex CLI; do not claim the CLI proof for them unless they expose compatible skill discovery. Fallback is opening or pasting the generated `SKILL.md`.
|
|
105
114
|
- `npx -y gsdd-cli health` now compares any installed generated runtime surfaces against current render output and routes repairs back through `npx -y gsdd-cli update`.
|
|
106
115
|
- Portable lifecycle contracts now align to the roadmap template status grammar: `[ ]`, `[-]`, `[x]`.
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
<role>
|
|
2
2
|
You are the EXECUTOR. Your job is to implement the tasks from a phase plan with precision and discipline.
|
|
3
3
|
|
|
4
|
-
You follow the plan
|
|
5
|
-
You DO NOT freelance. You DO NOT add features outside the plan.
|
|
4
|
+
You follow the plan, verify before reporting completion, document deviations, and DO NOT freelance or add features outside the plan.
|
|
6
5
|
</role>
|
|
7
6
|
|
|
8
7
|
<load_context>
|
|
@@ -36,7 +35,8 @@ Treat the preflight as an authorization seam over shared repo truth only:
|
|
|
36
35
|
- it does not mutate `.planning/ROADMAP.md` by itself
|
|
37
36
|
- owned writes remain execution artifacts, and ROADMAP mutation stays explicit in `<state_updates>` via `node .planning/bin/gsdd.mjs phase-status`
|
|
38
37
|
</lifecycle_preflight>
|
|
39
|
-
|
|
38
|
+
<control_map_check>Before code mutation, run `node .planning/bin/gsdd.mjs control-map --json` when available. Confirm the intended execution surface, dirty buckets, sibling/detached worktrees, and overlapping write-set risk. If it reports stale annotations, dubious git access, dirty out-of-plan canonical files, or unannotated dirty sibling worktrees, stop or ask for explicit acknowledgement before broad writes. Local annotations are intent hints only; computed repo/worktree truth stays primary.
|
|
39
|
+
</control_map_check>
|
|
40
40
|
<runtime_contract>
|
|
41
41
|
Execution uses the same `Runtime` and `Assurance` types as planning and verification.
|
|
42
42
|
Infer runtime from the launching surface when obvious: `.claude/` -> `claude-code`, `.codex/` or Codex portable skill -> `codex-cli`, `.opencode/` -> `opencode`, otherwise `other`.
|
|
@@ -39,6 +39,8 @@ Store the detected work type as `$WORK_TYPE` (one of: `phase`, `quick`, `generic
|
|
|
39
39
|
<gather_state>
|
|
40
40
|
Build a draft checkpoint from artifact truth before asking the user to restate work. The user should correct the draft, not rewrite obvious repo state from scratch.
|
|
41
41
|
|
|
42
|
+
When available, run `node .planning/bin/gsdd.mjs control-map --json` and use it as the draft's repo/worktree snapshot: canonical branch/HEAD, dirty tracked/untracked/ignored buckets, sibling/detached worktrees, stale annotations, planning drift, and recommended interventions. Include only a compact summary or pointer in `.planning/.continue-here.md`; the checkpoint records resumability context, not a replacement for future computed repo truth.
|
|
43
|
+
|
|
42
44
|
Ask the user conversationally to fill in the gaps the artifacts cannot answer:
|
|
43
45
|
|
|
44
46
|
1. **What was completed** this session
|
|
@@ -32,12 +32,12 @@ If the preflight result is `blocked`, STOP and report the blocker instead of inf
|
|
|
32
32
|
|
|
33
33
|
<integration_surface_check>
|
|
34
34
|
Before planning roadmap work, inspect the live integration surface separately from checkpoint or planning artifacts:
|
|
35
|
-
-
|
|
36
|
-
- divergence
|
|
37
|
-
-
|
|
38
|
-
- whether the current branch appears stale/spent or mixed-scope
|
|
35
|
+
- Run `node .planning/bin/gsdd.mjs control-map --json` when available.
|
|
36
|
+
- Use its computed branch/HEAD, divergence, tracked/untracked/ignored buckets, sibling/detached worktrees, local annotations, and interventions.
|
|
37
|
+
- If the helper is unavailable, fall back to direct git/worktree inspection.
|
|
39
38
|
|
|
40
|
-
If the planning truth says "next phase is X" but the git/worktree truth says the current branch is a stale or mixed execution surface, warn explicitly and treat the dirty branch as evidence only. Do not silently assume the checked-out branch is the right planning surface just because it exists.
|
|
39
|
+
If the planning truth says "next phase is X" but the git/worktree truth says the current branch is a stale/spent or mixed-scope execution surface, warn explicitly and treat the dirty branch as evidence only. Do not silently assume the checked-out branch is the right planning surface just because it exists.
|
|
40
|
+
Local annotations explain operator intent but do not outrank repo truth, planning artifacts, or checkpoint reconciliation.
|
|
41
41
|
</integration_surface_check>
|
|
42
42
|
|
|
43
43
|
<runtime_contract>
|
|
@@ -6,6 +6,10 @@ Core mindset: derive state from primary artifacts. ROADMAP.md checkboxes, phase
|
|
|
6
6
|
Scope boundary: you are NOT resume.md. You do not wait for user input, clean up checkpoints, present interactive menus, or trigger any action. You report and suggest only.
|
|
7
7
|
</role>
|
|
8
8
|
|
|
9
|
+
<control_map>
|
|
10
|
+
At the start of status reporting, run `node .planning/bin/gsdd.mjs control-map --json` when the local helper exists. Summarize its computed repo/worktree/planning state in the status block: canonical branch/HEAD, tracked/untracked dirty buckets, whether ignored paths were scanned, sibling or detached worktrees, stale local annotations, planning drift, and recommended interventions. Use `--with-ignored` before making a clean-workspace claim that includes ignored or generated surfaces. Treat the command output as read-only computed evidence. Local annotations under `.planning/.local/` explain intent but never outrank repo truth, planning artifacts, or checkpoint reconciliation.
|
|
11
|
+
</control_map>
|
|
12
|
+
|
|
9
13
|
<prerequisites>
|
|
10
14
|
`.planning/` must exist (from `npx -y gsdd-cli init`, or `gsdd init` when globally installed).
|
|
11
15
|
|
|
@@ -45,7 +45,7 @@ Store the response as `$DESCRIPTION`. If empty, re-prompt.
|
|
|
45
45
|
6. If `.planning/codebase/` exists, read whichever of `.planning/codebase/ARCHITECTURE.md`, `.planning/codebase/STACK.md`, `.planning/codebase/CONVENTIONS.md`, and `.planning/codebase/CONCERNS.md` are present. Summarize key findings from available docs in <=500 words as `$CODEBASE_CONTEXT`, emphasizing: safest surfaces to touch, risky zones to avoid, must-know conventions/traps, and what must be re-verified after change. Note any missing docs in the summary.
|
|
46
46
|
7. If `.planning/codebase/` does not exist, build a just-enough inline brownfield baseline instead of stopping. Read the repo root guidance that is cheap and stable (`README.md`, root manifest such as `package.json` / `pyproject.toml` / `Cargo.toml` when present, top-level app entrypoints, and any obviously relevant config or module files surfaced by `$DESCRIPTION`). Summarize the findings in <=500 words as `$CODEBASE_CONTEXT`, explicitly labeling it as a provisional baseline and calling out unknowns. Emphasize: likely implementation surface, likely dependency boundaries, conventions already visible, risky areas to avoid touching blindly, and what must be re-verified after the change. If the repo is still too unclear after this pass, keep that uncertainty explicit so Step 3.6 can recommend `/gsdd-map-codebase`.
|
|
47
47
|
8. **Session-boundary fallback:** If `.planning/.continue-here.bak` exists, read its `<judgment>` section. Use `<active_constraints>` and `<anti_regression>` rules as task-scoping context (do not violate active constraints; do not regress on listed invariants). After reading, run `node .planning/bin/gsdd.mjs file-op delete .planning/.continue-here.bak --missing ok` (auto-clean).
|
|
48
|
-
9. Inspect the live branch/worktree surface separately from checkpoint or planning artifacts.
|
|
48
|
+
9. Inspect the live branch/worktree surface separately from checkpoint or planning artifacts. Run `node .planning/bin/gsdd.mjs control-map --json` when available and use its computed repo/worktree/planning truth to identify stale/spent branches, dirty tracked/untracked/ignored buckets, sibling or detached worktrees, local annotations, and cleanup obligations. This is advisory for quick tasks unless the mismatch makes the task description materially misleading; local annotations are intent hints, not product truth.
|
|
49
49
|
|
|
50
50
|
If `.planning/quick/` does not exist, create it along with an empty `LOG.md`:
|
|
51
51
|
|
|
@@ -35,6 +35,10 @@ Treat the preflight as an authorization seam over shared repo truth only:
|
|
|
35
35
|
|
|
36
36
|
<process>
|
|
37
37
|
|
|
38
|
+
<control_map_reconciliation>
|
|
39
|
+
Before routing from a checkpoint, run `node .planning/bin/gsdd.mjs control-map --json` when available. Reconcile the checkpoint narrative against computed repo/worktree/planning truth: canonical branch/HEAD, tracked/untracked/ignored dirty buckets, sibling or detached worktrees, local annotations, active brownfield anchors, and planning drift. If the checkpoint understates dirty work, points at a stale branch/worktree, or conflicts with the active planning/brownfield surface, stop and present the mismatch before recommending execution. Local annotations are useful intent hints, not authority.
|
|
40
|
+
</control_map_reconciliation>
|
|
41
|
+
|
|
38
42
|
<detect_state>
|
|
39
43
|
Check for project artifacts in order:
|
|
40
44
|
|