gsdd-cli 0.27.0 → 0.28.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 +53 -22
- package/agents/DISTILLATION.md +2 -2
- package/agents/README.md +4 -4
- package/agents/approach-explorer.md +4 -4
- package/agents/executor.md +14 -14
- package/agents/integration-checker.md +2 -2
- package/agents/researcher.md +2 -2
- package/agents/roadmapper.md +6 -6
- package/agents/synthesizer.md +18 -18
- package/agents/verifier.md +2 -2
- package/bin/adapters/agents.mjs +3 -3
- package/bin/adapters/claude.mjs +16 -14
- package/bin/adapters/opencode.mjs +15 -12
- package/bin/gsdd.mjs +16 -13
- package/bin/lib/{models.mjs → config.mjs} +23 -16
- package/bin/lib/control-map.mjs +17 -488
- package/bin/lib/health-truth.mjs +8 -13
- package/bin/lib/health.mjs +25 -39
- package/bin/lib/init-flow.mjs +44 -38
- package/bin/lib/init-prompts.mjs +3 -3
- package/bin/lib/init-runtime.mjs +11 -30
- package/bin/lib/lifecycle-preflight.mjs +97 -410
- package/bin/lib/lifecycle-state.mjs +2 -1
- package/bin/lib/next.mjs +243 -20
- package/bin/lib/phase.mjs +10 -315
- package/bin/lib/rendering.mjs +64 -44
- package/bin/lib/runtime-freshness.mjs +18 -15
- package/bin/lib/state-dir.mjs +45 -0
- package/bin/lib/templates.mjs +59 -22
- package/bin/lib/work-context.mjs +12 -1
- package/bin/lib/workflows.mjs +0 -1
- package/bin/lib/workspace-root.mjs +11 -6
- package/distilled/DESIGN.md +58 -2
- package/distilled/EVIDENCE-INDEX.md +15 -2
- package/distilled/README.md +23 -33
- package/distilled/SKILL.md +9 -10
- package/distilled/templates/agents.block.md +5 -5
- package/distilled/templates/approach.md +3 -3
- package/distilled/templates/auth-matrix.md +2 -2
- package/distilled/templates/brownfield-change/CHANGE.md +1 -1
- package/distilled/templates/delegates/approach-explorer.md +5 -5
- package/distilled/templates/delegates/mapper-arch.md +3 -3
- package/distilled/templates/delegates/mapper-concerns.md +4 -4
- package/distilled/templates/delegates/mapper-quality.md +3 -3
- package/distilled/templates/delegates/mapper-tech.md +3 -3
- package/distilled/templates/delegates/plan-checker.md +5 -5
- package/distilled/templates/delegates/researcher-architecture.md +3 -3
- package/distilled/templates/delegates/researcher-features.md +3 -3
- package/distilled/templates/delegates/researcher-pitfalls.md +3 -3
- package/distilled/templates/delegates/researcher-stack.md +3 -3
- package/distilled/templates/delegates/researcher-synthesizer.md +7 -7
- package/distilled/templates/research/architecture.md +1 -1
- package/distilled/templates/research/pitfalls.md +1 -1
- package/distilled/templates/research/stack.md +1 -1
- package/distilled/templates/roadmap.md +4 -4
- package/distilled/templates/spec.md +2 -2
- package/distilled/workflows/audit-milestone.md +22 -22
- package/distilled/workflows/complete-milestone.md +35 -35
- package/distilled/workflows/execute.md +29 -29
- package/distilled/workflows/map-codebase.md +30 -30
- package/distilled/workflows/new-milestone.md +18 -18
- package/distilled/workflows/new-project.md +45 -45
- package/distilled/workflows/pause.md +15 -15
- package/distilled/workflows/plan.md +63 -63
- package/distilled/workflows/progress.md +40 -39
- package/distilled/workflows/quick.md +43 -43
- package/distilled/workflows/resume.md +23 -22
- package/distilled/workflows/verify-work.md +7 -7
- package/distilled/workflows/verify.md +20 -20
- package/docs/BROWNFIELD-PROOF.md +1 -1
- package/docs/RUNTIME-SUPPORT.md +13 -13
- package/docs/USER-GUIDE.md +17 -20
- package/docs/claude/context-monitor.md +1 -1
- package/docs/proof/consumer-node-cli/README.md +1 -1
- package/package.json +3 -3
- package/bin/lib/closeout-report.mjs +0 -318
- package/bin/lib/evidence-contract.mjs +0 -325
- package/bin/lib/provenance.mjs +0 -390
- package/bin/lib/session-fingerprint.mjs +0 -223
- package/bin/lib/ui-proof.mjs +0 -1007
- package/distilled/workflows/plan-milestone-gaps.md +0 -204
|
@@ -293,7 +293,8 @@ export function readBrownfieldChangeState(planningDir) {
|
|
|
293
293
|
}
|
|
294
294
|
|
|
295
295
|
export function deriveNonPhaseState({ planningDir, hasSpec, hasRoadmap, brownfieldChange } = {}) {
|
|
296
|
-
|
|
296
|
+
const brownfieldStatus = String(brownfieldChange?.currentStatus || '').trim().toLowerCase();
|
|
297
|
+
if (brownfieldChange?.exists && brownfieldStatus !== 'closed') return 'active_brownfield_change';
|
|
297
298
|
if (hasRoadmap) return null;
|
|
298
299
|
if (hasSpec) return 'between_milestones';
|
|
299
300
|
if (hasSubstantiveCodebaseMaps(planningDir)) return 'codebase_only';
|
package/bin/lib/next.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import { join } from 'path';
|
|
|
2
2
|
import { existsSync } from 'fs';
|
|
3
3
|
import { output, parseFlagValue } from './cli-utils.mjs';
|
|
4
4
|
import { buildControlMap } from './control-map.mjs';
|
|
5
|
+
import { resolveStateDir } from './state-dir.mjs';
|
|
5
6
|
import {
|
|
6
7
|
NEXT_STATES,
|
|
7
8
|
addOpenQuestion,
|
|
@@ -68,6 +69,9 @@ function packet(overrides) {
|
|
|
68
69
|
confidence: overrides.confidence || 'medium',
|
|
69
70
|
next_command: overrides.next_command || null,
|
|
70
71
|
next_action: overrides.next_action || null,
|
|
72
|
+
authority: overrides.authority || null,
|
|
73
|
+
route_kind: overrides.route_kind || null,
|
|
74
|
+
blocked_by: overrides.blocked_by || [],
|
|
71
75
|
requires_user: Boolean(overrides.requires_user),
|
|
72
76
|
questions: overrides.questions || [],
|
|
73
77
|
constraints: overrides.constraints || [],
|
|
@@ -110,6 +114,15 @@ function manualReviewAction(targets, description) {
|
|
|
110
114
|
};
|
|
111
115
|
}
|
|
112
116
|
|
|
117
|
+
function stateDirName(context) {
|
|
118
|
+
return context?.planning?.state_dir_name || '.work';
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function statePath(context, relativePath = '') {
|
|
122
|
+
const dirName = stateDirName(context);
|
|
123
|
+
return relativePath ? `${dirName}/${relativePath}` : dirName;
|
|
124
|
+
}
|
|
125
|
+
|
|
113
126
|
function userQuestionAction(questionIds, description) {
|
|
114
127
|
return {
|
|
115
128
|
type: 'user_question',
|
|
@@ -120,9 +133,10 @@ function userQuestionAction(questionIds, description) {
|
|
|
120
133
|
|
|
121
134
|
function summarizeControlMap(cwd) {
|
|
122
135
|
try {
|
|
136
|
+
const stateDir = resolveStateDir(cwd).dir;
|
|
123
137
|
return buildControlMap({
|
|
124
138
|
workspaceRoot: cwd,
|
|
125
|
-
planningDir:
|
|
139
|
+
planningDir: stateDir,
|
|
126
140
|
});
|
|
127
141
|
} catch (error) {
|
|
128
142
|
return {
|
|
@@ -145,10 +159,29 @@ function readManifestStatus(context) {
|
|
|
145
159
|
|
|
146
160
|
function repoWarningsFromControlMap(controlMap) {
|
|
147
161
|
return (controlMap.risks || [])
|
|
148
|
-
.filter((risk) => risk.code === 'canonical_dirty')
|
|
162
|
+
.filter((risk) => risk.code === 'canonical_dirty' || risk.severity === 'block')
|
|
149
163
|
.map((risk) => risk.message);
|
|
150
164
|
}
|
|
151
165
|
|
|
166
|
+
function controlMapBlockers(controlMap) {
|
|
167
|
+
return (controlMap.risks || [])
|
|
168
|
+
.filter((risk) => risk.severity === 'block')
|
|
169
|
+
.map((risk) => risk.code);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function hasActiveBrownfieldChange(context) {
|
|
173
|
+
return brownfieldPosture(context) === 'active';
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function brownfieldPosture(context) {
|
|
177
|
+
if (!context.planning.has_brownfield_change) return null;
|
|
178
|
+
const status = String(context.planning.brownfield_change?.currentStatus || '').trim().toLowerCase();
|
|
179
|
+
if (status === 'closed') return 'closed';
|
|
180
|
+
if (status === 'ready_for_verification') return 'ready_for_verification';
|
|
181
|
+
if (status === 'blocked') return 'blocked';
|
|
182
|
+
return 'active';
|
|
183
|
+
}
|
|
184
|
+
|
|
152
185
|
function routeFromStateObject(stateValue) {
|
|
153
186
|
const state = stateValue || {};
|
|
154
187
|
const workflow = state.workflow || state.milestone || state;
|
|
@@ -277,6 +310,7 @@ function routeNext(ctx) {
|
|
|
277
310
|
if (context.milestone?.has_audit) inputsConsidered.push('.work/milestone/AUDIT.md');
|
|
278
311
|
else inputsSkipped.push('.work/milestone/AUDIT.md: missing');
|
|
279
312
|
if (context.milestone?.phase_packet_count > 0) inputsConsidered.push('.work/milestone/phases/*');
|
|
313
|
+
if (context.planning.has_brownfield_change) inputsConsidered.push(statePath(context, 'brownfield-change/CHANGE.md'));
|
|
280
314
|
|
|
281
315
|
if (context.graph.invalid.length > 0) {
|
|
282
316
|
return packet({
|
|
@@ -423,8 +457,114 @@ function routeNext(ctx) {
|
|
|
423
457
|
}, { context, controlMap, constraints, privacyNotes, inputsConsidered, inputsSkipped, traceRefs });
|
|
424
458
|
}
|
|
425
459
|
|
|
460
|
+
const brownfieldStatus = brownfieldPosture(context);
|
|
461
|
+
if (['active', 'blocked', 'ready_for_verification'].includes(brownfieldStatus) && context.milestone?.exists) {
|
|
462
|
+
return packet({
|
|
463
|
+
state: 'blocked',
|
|
464
|
+
reason: 'Active brownfield-change authority and .work/milestone authority both exist; continuing would silently choose between two continuity roots.',
|
|
465
|
+
confidence: 'high',
|
|
466
|
+
next_command: null,
|
|
467
|
+
next_action: manualReviewAction([statePath(context, 'brownfield-change/CHANGE.md'), '.work/milestone/MILESTONE.md', '.work/milestone/ROADMAP.md'], 'Resolve the authority conflict before routing to plan, execute, or verify.'),
|
|
468
|
+
authority: 'blocked',
|
|
469
|
+
route_kind: 'authority_conflict',
|
|
470
|
+
blocked_by: ['brownfield_change', 'work_milestone'],
|
|
471
|
+
requires_user: false,
|
|
472
|
+
constraints,
|
|
473
|
+
evidence_required: ['One continuity authority must be selected or archived before continuing.'],
|
|
474
|
+
artifacts_to_read: [statePath(context, 'brownfield-change/CHANGE.md'), '.work/milestone/MILESTONE.md', '.work/milestone/ROADMAP.md'],
|
|
475
|
+
repo_warnings: repoWarningsFromControlMap(controlMap),
|
|
476
|
+
privacy_notes: privacyNotes,
|
|
477
|
+
inputs_considered: inputsConsidered,
|
|
478
|
+
inputs_skipped: inputsSkipped,
|
|
479
|
+
trace_refs: traceRefs,
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
if (brownfieldStatus === 'blocked') {
|
|
484
|
+
return packet({
|
|
485
|
+
state: 'blocked',
|
|
486
|
+
reason: 'Active bounded brownfield change is marked blocked; resolve the blocker recorded in CHANGE.md before planning or execution.',
|
|
487
|
+
confidence: 'high',
|
|
488
|
+
next_command: null,
|
|
489
|
+
next_action: manualReviewAction([statePath(context, 'brownfield-change/CHANGE.md'), statePath(context, 'brownfield-change/HANDOFF.md')], 'Resolve the blocker and update the brownfield change status before continuing.'),
|
|
490
|
+
authority: 'brownfield_change',
|
|
491
|
+
route_kind: 'brownfield_change_blocked',
|
|
492
|
+
blocked_by: ['brownfield_change'],
|
|
493
|
+
requires_user: false,
|
|
494
|
+
constraints,
|
|
495
|
+
artifacts_to_read: [statePath(context, 'brownfield-change/CHANGE.md'), statePath(context, 'brownfield-change/HANDOFF.md')],
|
|
496
|
+
artifacts_to_write: [statePath(context, 'brownfield-change/CHANGE.md'), statePath(context, 'brownfield-change/HANDOFF.md')],
|
|
497
|
+
repo_warnings: repoWarningsFromControlMap(controlMap),
|
|
498
|
+
privacy_notes: privacyNotes,
|
|
499
|
+
inputs_considered: inputsConsidered,
|
|
500
|
+
inputs_skipped: inputsSkipped,
|
|
501
|
+
trace_refs: traceRefs,
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if (brownfieldStatus === 'ready_for_verification') {
|
|
506
|
+
return packet({
|
|
507
|
+
state: 'verify',
|
|
508
|
+
reason: 'Active bounded brownfield change is ready for verification; verify the bounded closeout proof before more planning.',
|
|
509
|
+
confidence: 'high',
|
|
510
|
+
next_command: null,
|
|
511
|
+
next_action: manualReviewAction([statePath(context, 'brownfield-change/CHANGE.md'), statePath(context, 'brownfield-change/VERIFICATION.md')], 'Verify the bounded brownfield change and update VERIFICATION.md.'),
|
|
512
|
+
authority: 'brownfield_change',
|
|
513
|
+
route_kind: 'brownfield_change_verification',
|
|
514
|
+
requires_user: false,
|
|
515
|
+
constraints,
|
|
516
|
+
evidence_required: ['VERIFICATION.md must prove the CHANGE.md done-when and closeout path or record concrete gaps.'],
|
|
517
|
+
artifacts_to_read: [
|
|
518
|
+
statePath(context, 'brownfield-change/CHANGE.md'),
|
|
519
|
+
statePath(context, 'brownfield-change/HANDOFF.md'),
|
|
520
|
+
statePath(context, 'brownfield-change/VERIFICATION.md'),
|
|
521
|
+
],
|
|
522
|
+
artifacts_to_write: [statePath(context, 'brownfield-change/VERIFICATION.md')],
|
|
523
|
+
repo_warnings: repoWarningsFromControlMap(controlMap),
|
|
524
|
+
privacy_notes: privacyNotes,
|
|
525
|
+
inputs_considered: inputsConsidered,
|
|
526
|
+
inputs_skipped: inputsSkipped,
|
|
527
|
+
trace_refs: traceRefs,
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
if (brownfieldStatus === 'active') {
|
|
532
|
+
return packet({
|
|
533
|
+
state: 'plan',
|
|
534
|
+
reason: 'Active bounded brownfield change exists; route planning through the brownfield change lane before phase roadmap preflight.',
|
|
535
|
+
confidence: 'high',
|
|
536
|
+
next_command: 'gsdd-plan',
|
|
537
|
+
next_action: workflowAction('gsdd-plan', 'Plan the active bounded brownfield change from CHANGE.md and HANDOFF.md without requiring unrelated ROADMAP phase membership.'),
|
|
538
|
+
authority: 'brownfield_change',
|
|
539
|
+
route_kind: 'brownfield_change',
|
|
540
|
+
requires_user: false,
|
|
541
|
+
constraints: [
|
|
542
|
+
...constraints,
|
|
543
|
+
`Bounded brownfield changes use \`${statePath(context, 'brownfield-change/')}\`, not \`${statePath(context, 'phases/')}\` or ROADMAP checkboxes.`,
|
|
544
|
+
'Promote to `gsdd-new-project` or `gsdd-new-milestone` only when the change no longer fits one active stream.',
|
|
545
|
+
],
|
|
546
|
+
evidence_required: ['Plan must preserve the bounded CHANGE.md goal, scope, done-when, next action, and closeout path.'],
|
|
547
|
+
artifacts_to_read: [
|
|
548
|
+
statePath(context, 'brownfield-change/CHANGE.md'),
|
|
549
|
+
statePath(context, 'brownfield-change/HANDOFF.md'),
|
|
550
|
+
statePath(context, 'brownfield-change/VERIFICATION.md'),
|
|
551
|
+
statePath(context, 'SPEC.md'),
|
|
552
|
+
statePath(context, 'ROADMAP.md'),
|
|
553
|
+
],
|
|
554
|
+
artifacts_to_write: [
|
|
555
|
+
statePath(context, 'brownfield-change/CHANGE.md'),
|
|
556
|
+
statePath(context, 'brownfield-change/HANDOFF.md'),
|
|
557
|
+
],
|
|
558
|
+
repo_warnings: repoWarningsFromControlMap(controlMap),
|
|
559
|
+
privacy_notes: privacyNotes,
|
|
560
|
+
inputs_considered: inputsConsidered,
|
|
561
|
+
inputs_skipped: inputsSkipped,
|
|
562
|
+
trace_refs: traceRefs,
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
|
|
426
566
|
if (hasUnverifiedSummaries(context.planning.phases)) {
|
|
427
|
-
return enrichRoute({ state: 'verify', reason:
|
|
567
|
+
return enrichRoute({ state: 'verify', reason: `${statePath(context)} phase summaries exist without matching verification reports.` }, { context, controlMap, constraints, privacyNotes, inputsConsidered, inputsSkipped, traceRefs });
|
|
428
568
|
}
|
|
429
569
|
|
|
430
570
|
const workMilestoneRoute = routeFromWorkMilestone(context, manifest);
|
|
@@ -436,14 +576,18 @@ function routeNext(ctx) {
|
|
|
436
576
|
if (!legacyComplete) {
|
|
437
577
|
return packet({
|
|
438
578
|
state: 'plan',
|
|
439
|
-
reason:
|
|
579
|
+
reason: `\`.work/goal.md\` exists, but canonical ${statePath(context)} lifecycle truth is incomplete; create or refresh the Workspine-native plan from \`.work\`.`,
|
|
440
580
|
confidence: context.planning.exists ? 'medium' : 'high',
|
|
441
581
|
next_command: 'gsdd-plan',
|
|
442
582
|
next_action: workflowAction('gsdd-plan', 'Plan the Workspine-native milestone from `.work` truth.'),
|
|
583
|
+
authority: 'work',
|
|
584
|
+
route_kind: 'work_native_plan',
|
|
585
|
+
blocked_by: controlMapBlockers(controlMap),
|
|
586
|
+
repo_warnings: repoWarningsFromControlMap(controlMap),
|
|
443
587
|
requires_user: false,
|
|
444
588
|
constraints: [
|
|
445
589
|
...constraints,
|
|
446
|
-
|
|
590
|
+
`Do not infer normal ${statePath(context)} milestone progress when SPEC.md, ROADMAP.md, or MILESTONES.md are missing.`,
|
|
447
591
|
],
|
|
448
592
|
evidence_required: ['Plan must map `.work/goal.md` requirements to implementation and verification artifacts.'],
|
|
449
593
|
artifacts_to_read: ['.work/goal.md', '.work/research/2026-06-20-long-term-agent-harness-consistency.md'],
|
|
@@ -452,9 +596,9 @@ function routeNext(ctx) {
|
|
|
452
596
|
inputs_considered: inputsConsidered,
|
|
453
597
|
inputs_skipped: [
|
|
454
598
|
...inputsSkipped,
|
|
455
|
-
!context.planning.has_spec ? '
|
|
456
|
-
!context.planning.has_roadmap ? '
|
|
457
|
-
!context.planning.has_milestones ? '
|
|
599
|
+
!context.planning.has_spec ? `${statePath(context, 'SPEC.md')}: missing` : null,
|
|
600
|
+
!context.planning.has_roadmap ? `${statePath(context, 'ROADMAP.md')}: missing` : null,
|
|
601
|
+
!context.planning.has_milestones ? `${statePath(context, 'MILESTONES.md')}: missing` : null,
|
|
458
602
|
].filter(Boolean),
|
|
459
603
|
trace_refs: traceRefs,
|
|
460
604
|
});
|
|
@@ -466,9 +610,13 @@ function routeNext(ctx) {
|
|
|
466
610
|
confidence: 'medium',
|
|
467
611
|
next_command: 'gsdd-plan',
|
|
468
612
|
next_action: workflowAction('gsdd-plan', 'Plan the next approved work slice.'),
|
|
613
|
+
authority: 'planning',
|
|
614
|
+
route_kind: 'phase_plan',
|
|
615
|
+
blocked_by: controlMapBlockers(controlMap),
|
|
616
|
+
repo_warnings: repoWarningsFromControlMap(controlMap),
|
|
469
617
|
requires_user: false,
|
|
470
618
|
constraints,
|
|
471
|
-
artifacts_to_read: ['.work/goal.md', '
|
|
619
|
+
artifacts_to_read: ['.work/goal.md', statePath(context, 'SPEC.md'), statePath(context, 'ROADMAP.md'), statePath(context, 'MILESTONES.md')],
|
|
472
620
|
artifacts_to_write: ['.work/focus/current.md'],
|
|
473
621
|
privacy_notes: privacyNotes,
|
|
474
622
|
inputs_considered: inputsConsidered,
|
|
@@ -523,7 +671,7 @@ function enrichRoute(route, { context, controlMap, constraints, privacyNotes, in
|
|
|
523
671
|
execute: workflowAction('gsdd-execute', 'Execute the approved Workspine plan.'),
|
|
524
672
|
verify: workflowAction('gsdd-verify', 'Verify executed artifacts against the plan.'),
|
|
525
673
|
audit: workflowAction('gsdd-audit-milestone', 'Audit milestone-level integration and closure evidence.'),
|
|
526
|
-
fix_gaps: workflowAction('gsdd-plan
|
|
674
|
+
fix_gaps: workflowAction('gsdd-plan', 'Plan amend/extend work from audit or verification findings.'),
|
|
527
675
|
dogfood: cliAction(['next', 'dogfood', 'capture', '--id', '<id>', '--title', '<text>', '--body', '<text>'], 'Capture one bounded local dogfood finding.'),
|
|
528
676
|
pause: manualReviewAction(['.work/handoff/current.md'], 'Update handoff before pausing.'),
|
|
529
677
|
blocked: null,
|
|
@@ -537,6 +685,9 @@ function enrichRoute(route, { context, controlMap, constraints, privacyNotes, in
|
|
|
537
685
|
confidence: route.state === 'blocked' || route.state === 'ask_user' ? 'high' : 'medium',
|
|
538
686
|
next_command: route.next_command || actionToLegacyCommand(nextAction),
|
|
539
687
|
next_action: nextAction,
|
|
688
|
+
authority: route.authority || inferAuthorityForState(route.state, context),
|
|
689
|
+
route_kind: route.route_kind || route.state,
|
|
690
|
+
blocked_by: [...(route.blocked_by || []), ...controlMapBlockers(controlMap)],
|
|
540
691
|
requires_user: route.state === 'ask_user',
|
|
541
692
|
questions: route.questions || [],
|
|
542
693
|
constraints,
|
|
@@ -551,6 +702,13 @@ function enrichRoute(route, { context, controlMap, constraints, privacyNotes, in
|
|
|
551
702
|
});
|
|
552
703
|
}
|
|
553
704
|
|
|
705
|
+
function inferAuthorityForState(state, context) {
|
|
706
|
+
if (context.milestone?.exists) return 'work_milestone';
|
|
707
|
+
if (hasActiveBrownfieldChange(context)) return 'brownfield_change';
|
|
708
|
+
if (state === 'blocked') return 'blocked';
|
|
709
|
+
return 'planning';
|
|
710
|
+
}
|
|
711
|
+
|
|
554
712
|
function actionToLegacyCommand(action) {
|
|
555
713
|
if (!action) return null;
|
|
556
714
|
if (action.type === 'cli_command') return action.command;
|
|
@@ -562,11 +720,11 @@ function defaultReadArtifacts(state, context) {
|
|
|
562
720
|
if (state === 'execute') return ['.work/goal.md', '.work/focus/current.md'];
|
|
563
721
|
if (state === 'verify') return context.milestone?.has_roadmap
|
|
564
722
|
? ['.work/goal.md', '.work/milestone/ROADMAP.md', '.work/milestone/phases/*/*-VERIFY.md']
|
|
565
|
-
: ['.work/goal.md', '.work/evidence/manifest.json', '
|
|
723
|
+
: ['.work/goal.md', '.work/evidence/manifest.json', statePath(context, 'phases/*/*-SUMMARY.md')];
|
|
566
724
|
if (state === 'audit') return context.milestone?.has_roadmap
|
|
567
725
|
? ['.work/goal.md', '.work/milestone/ROADMAP.md', '.work/milestone/phases/*/*-VERIFY.md']
|
|
568
|
-
: ['.work/goal.md', '.work/evidence/manifest.json', '
|
|
569
|
-
if (state === 'fix_gaps') return ['.work/evidence/manifest.json'];
|
|
726
|
+
: ['.work/goal.md', '.work/evidence/manifest.json', statePath(context, 'phases/**/*-VERIFICATION.md')];
|
|
727
|
+
if (state === 'fix_gaps') return ['.work/evidence/manifest.json', '.work/*-MILESTONE-AUDIT.md', '.work/milestone/AUDIT.md'];
|
|
570
728
|
if (state === 'dogfood') return ['.work/goal.md', '.work/evidence/manifest.json'];
|
|
571
729
|
if (state === 'pause') return ['.work/handoff/current.md'];
|
|
572
730
|
if (state === 'complete') return ['.work/goal.md', '.work/evidence/manifest.json', '.work/dogfood/'];
|
|
@@ -577,7 +735,7 @@ function defaultReadArtifacts(state, context) {
|
|
|
577
735
|
function defaultWriteArtifacts(state) {
|
|
578
736
|
if (state === 'verify') return ['.work/evidence/manifest.json'];
|
|
579
737
|
if (state === 'audit') return ['.work/evidence/manifest.json'];
|
|
580
|
-
if (state === 'fix_gaps') return ['.work/focus/current.md', '.work/graph/events.jsonl'];
|
|
738
|
+
if (state === 'fix_gaps') return ['.work/ROADMAP.md', '.work/phases/', '.work/focus/current.md', '.work/graph/events.jsonl'];
|
|
581
739
|
if (state === 'dogfood') return ['.work/dogfood/*.md', '.work/graph/events.jsonl'];
|
|
582
740
|
if (state === 'pause') return ['.work/handoff/current.md'];
|
|
583
741
|
return [];
|
|
@@ -601,13 +759,78 @@ function findTrustGate(manifest) {
|
|
|
601
759
|
};
|
|
602
760
|
}
|
|
603
761
|
|
|
762
|
+
const CARD_WIDTH = 62;
|
|
763
|
+
|
|
764
|
+
const STATE_LABELS = {
|
|
765
|
+
research: 'Look into the problem before planning',
|
|
766
|
+
plan: 'Plan the next piece of work',
|
|
767
|
+
execute: 'Build the planned work',
|
|
768
|
+
verify: 'Prove the last piece of work is done',
|
|
769
|
+
audit: 'Check the whole milestone holds together',
|
|
770
|
+
fix_gaps: 'Plan the gaps that checking found',
|
|
771
|
+
dogfood: 'Use the result and record one honest finding',
|
|
772
|
+
complete: 'Finish and archive the milestone',
|
|
773
|
+
ask_user: 'Answer a question before work can continue',
|
|
774
|
+
pause: 'Work is paused; pick it back up when ready',
|
|
775
|
+
blocked: 'Work is stuck; clear the blocker below',
|
|
776
|
+
};
|
|
777
|
+
|
|
778
|
+
function cardFrame(text) {
|
|
779
|
+
return `│${text.padEnd(CARD_WIDTH)}│`;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
function pushCardLine(rows, indent, hang, text) {
|
|
783
|
+
const words = String(text).split(/\s+/).filter(Boolean);
|
|
784
|
+
if (words.length === 0) {
|
|
785
|
+
rows.push(cardFrame(' '.repeat(indent)));
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
let prefix = ' '.repeat(indent);
|
|
789
|
+
let line = prefix;
|
|
790
|
+
for (const word of words) {
|
|
791
|
+
const candidate = line === prefix ? line + word : `${line} ${word}`;
|
|
792
|
+
if (candidate.length <= CARD_WIDTH) {
|
|
793
|
+
line = candidate;
|
|
794
|
+
} else {
|
|
795
|
+
rows.push(cardFrame(line));
|
|
796
|
+
prefix = ' '.repeat(hang);
|
|
797
|
+
line = prefix + word;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
rows.push(cardFrame(line));
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
export function renderNextCard(packetValue) {
|
|
804
|
+
const top = `┌${'─'.repeat(CARD_WIDTH)}┐`;
|
|
805
|
+
const rule = `├${'─'.repeat(CARD_WIDTH)}┤`;
|
|
806
|
+
const bottom = `└${'─'.repeat(CARD_WIDTH)}┘`;
|
|
807
|
+
const label = STATE_LABELS[packetValue.state] || packetValue.state;
|
|
808
|
+
const action = (packetValue.next_action ? renderAction(packetValue.next_action) : null)
|
|
809
|
+
|| packetValue.next_command
|
|
810
|
+
|| '(nothing queued)';
|
|
811
|
+
const waiting = packetValue.requires_user ? 'yes' : 'no';
|
|
812
|
+
|
|
813
|
+
const rows = [top];
|
|
814
|
+
rows.push(cardFrame(' Where things stand'));
|
|
815
|
+
rows.push(rule);
|
|
816
|
+
pushCardLine(rows, 2, 7, `Now: ${label}`);
|
|
817
|
+
pushCardLine(rows, 2, 7, `Why: ${packetValue.reason || ''}`);
|
|
818
|
+
rows.push(cardFrame(''));
|
|
819
|
+
rows.push(cardFrame(' Do this next:'));
|
|
820
|
+
pushCardLine(rows, 4, 4, action);
|
|
821
|
+
rows.push(cardFrame(''));
|
|
822
|
+
rows.push(cardFrame(` Waiting on you: ${waiting}`));
|
|
823
|
+
rows.push(cardFrame(''));
|
|
824
|
+
rows.push(cardFrame(' Stuck? Run: gsdd next --format human'));
|
|
825
|
+
rows.push(cardFrame(''));
|
|
826
|
+
rows.push(cardFrame(' Safety checks — this computer: not set up yet ·'));
|
|
827
|
+
rows.push(cardFrame(' server: not set up yet'));
|
|
828
|
+
rows.push(bottom);
|
|
829
|
+
return rows.join('\n');
|
|
830
|
+
}
|
|
831
|
+
|
|
604
832
|
function printHuman(packetValue) {
|
|
605
|
-
console.log(
|
|
606
|
-
console.log(`Why: ${packetValue.reason}`);
|
|
607
|
-
if (packetValue.next_action) console.log(`Next: ${renderAction(packetValue.next_action)}`);
|
|
608
|
-
else if (packetValue.next_command) console.log(`Next: ${packetValue.next_command}`);
|
|
609
|
-
if (packetValue.requires_user) console.log('Approval: required');
|
|
610
|
-
else console.log('Approval: not required');
|
|
833
|
+
console.log(renderNextCard(packetValue));
|
|
611
834
|
if (packetValue.questions.length > 0) {
|
|
612
835
|
console.log('\nQuestions:');
|
|
613
836
|
for (const question of packetValue.questions) {
|