@opengsd/gsd-core 1.16.0 → 1.17.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.
Files changed (58) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -2
  3. package/.opencode/plugins/gsd-core.js +251 -1
  4. package/.opencode/plugins/gsd-statusline/tui.tsx +55 -0
  5. package/GEMINI.md +1 -1
  6. package/agents/gsd-debug-session-manager.compact.md +6 -0
  7. package/agents/gsd-debug-session-manager.md +6 -0
  8. package/bin/gsd-mcp-server.js +3 -1
  9. package/bin/install.js +361 -94
  10. package/gsd-core/bin/gsd-tools.cjs +17 -2
  11. package/gsd-core/bin/lib/adr-parser.cjs +37 -5
  12. package/gsd-core/bin/lib/capability-registry.cjs +132 -80
  13. package/gsd-core/bin/lib/capability-validator.cjs +2 -1
  14. package/gsd-core/bin/lib/capability-writer.cjs +2 -1
  15. package/gsd-core/bin/lib/commands.cjs +28 -8
  16. package/gsd-core/bin/lib/gate-context-drift.cjs +5 -4
  17. package/gsd-core/bin/lib/gate-ui-safety.cjs +2 -1
  18. package/gsd-core/bin/lib/graphify.cjs +3 -2
  19. package/gsd-core/bin/lib/init.cjs +32 -12
  20. package/gsd-core/bin/lib/install-engine.cjs +11 -0
  21. package/gsd-core/bin/lib/install-shadow-report.cjs +12 -0
  22. package/gsd-core/bin/lib/installer-migrations/011-opencode-mcp-no-prompts.cjs +99 -0
  23. package/gsd-core/bin/lib/installer-migrations.cjs +54 -1
  24. package/gsd-core/bin/lib/jsonc-edit.cjs +418 -0
  25. package/gsd-core/bin/lib/loop-host-contract.cjs +2 -1
  26. package/gsd-core/bin/lib/mcp-server.cjs +5 -1
  27. package/gsd-core/bin/lib/opencode-mcp-entry.cjs +39 -0
  28. package/gsd-core/bin/lib/opencode-version.cjs +486 -0
  29. package/gsd-core/bin/lib/phase.cjs +21 -0
  30. package/gsd-core/bin/lib/quick-batch-dispatch.cjs +2 -1
  31. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +95 -4
  32. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +58 -3
  33. package/gsd-core/bin/lib/runtime-slash.cjs +13 -0
  34. package/gsd-core/bin/lib/smart-entry.cjs +92 -53
  35. package/gsd-core/bin/lib/state-transition.cjs +67 -9
  36. package/gsd-core/bin/lib/state.cjs +454 -66
  37. package/gsd-core/bin/lib/update-context.cjs +18 -1
  38. package/gsd-core/bin/lib/verification.cjs +107 -17
  39. package/gsd-core/workflows/code-review.md +16 -1
  40. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +1 -1
  41. package/gsd-core/workflows/execute-phase.md +3 -1
  42. package/gsd-core/workflows/resume-project.md +14 -10
  43. package/hooks/dist/gsd-check-update.js +26 -20
  44. package/hooks/dist/gsd-context-monitor.js +14 -5
  45. package/hooks/dist/gsd-secret-read-guard.js +117 -1
  46. package/hooks/dist/gsd-statusline.js +321 -22
  47. package/hooks/gsd-check-update.js +26 -20
  48. package/hooks/gsd-context-monitor.js +14 -5
  49. package/hooks/gsd-secret-read-guard.js +117 -1
  50. package/hooks/gsd-statusline.js +321 -22
  51. package/package.json +1 -1
  52. package/scripts/gen-capability-registry.cjs +7 -1
  53. package/scripts/gen-loop-host-contract.cjs +4 -1
  54. package/scripts/lib/macos-conformance-tier.generated.cjs +4 -0
  55. package/scripts/lib/platform-conformance-tier.generated.cjs +10 -0
  56. package/scripts/mutation-matrix.cjs +3 -1
  57. package/scripts/release-tarball-smoke.cjs +14 -7
  58. package/vscode/package.json +1 -1
@@ -417,7 +417,41 @@ function createStateWriteIntent(transaction, init = {}) {
417
417
  err.code = 'STATE_WRITE_INTENT_TRANSACTION_REQUIRED';
418
418
  throw err;
419
419
  }
420
- const assertions = Object.freeze((init.assertions ?? []).map((a) => Object.freeze({ field: a.field, requirement: a.requirement })));
420
+ // C2 (ADR-4629 §8.2): an unknown `requirement` is a construction error. §8.2
421
+ // judges only `'required'` assertions, so a mistyped value (`'Required'`)
422
+ // would otherwise be silently skipped: a fail-open verifier.
423
+ for (const a of init.assertions ?? []) {
424
+ if (a.requirement !== 'required' && a.requirement !== 'best-effort') {
425
+ const err = new Error(`createStateWriteIntent: assertion ${JSON.stringify(a.field)} has unknown requirement ` +
426
+ `${JSON.stringify(a.requirement)}; expected 'required' or 'best-effort' (ADR-4629 §8.1).`);
427
+ err.code = 'STATE_WRITE_INTENT_REQUIREMENT_INVALID';
428
+ throw err;
429
+ }
430
+ }
431
+ if (init.scope !== undefined && init.scope !== 'narrow' && init.scope !== 'broad') {
432
+ const err = new Error(`createStateWriteIntent: unknown scope ${JSON.stringify(init.scope)}; expected 'narrow' or 'broad' ` +
433
+ '(ADR-4629 §8.1).');
434
+ err.code = 'STATE_WRITE_INTENT_SCOPE_INVALID';
435
+ throw err;
436
+ }
437
+ // C2 (ADR-4629 §8.2): an unknown `target` is a construction error, never a
438
+ // silent fall-back to field semantics (a JS caller passing `'sections'`).
439
+ for (const a of init.assertions ?? []) {
440
+ if (a.target !== undefined && a.target !== 'field' && a.target !== 'section') {
441
+ const err = new Error(`createStateWriteIntent: assertion ${JSON.stringify(a.field)} has unknown target ` +
442
+ `${JSON.stringify(a.target)}; expected 'field' or 'section' (ADR-4629 §8.2).`);
443
+ err.code = 'STATE_WRITE_INTENT_TARGET_INVALID';
444
+ throw err;
445
+ }
446
+ }
447
+ // C2 (ADR-4629 §8.2): `target` / `value` are copied only when the caller set
448
+ // them, so a C1-shaped assertion stays exactly `{ field, requirement }`.
449
+ const assertions = Object.freeze((init.assertions ?? []).map((a) => Object.freeze({
450
+ field: a.field,
451
+ requirement: a.requirement,
452
+ ...(a.target !== undefined ? { target: a.target } : {}),
453
+ ...(a.value !== undefined ? { value: a.value } : {}),
454
+ })));
421
455
  return Object.freeze({
422
456
  ...transaction,
423
457
  assertions,
@@ -1575,9 +1609,30 @@ function advancePlanCore(content, deps) {
1575
1609
  data: { advanced: true, previous_plan: currentPlan, current_plan: newPlan, total_plans: totalPlans },
1576
1610
  };
1577
1611
  }
1578
- // ----------------------------------------------------------------------------
1579
- // completePhase — intent implementation (Phase 3)
1580
- // ----------------------------------------------------------------------------
1612
+ /**
1613
+ * #4824: Status / Current Plan / Stopped-at wording for the phase a
1614
+ * completion hands off to. The historical `Ready to plan` is only true for a
1615
+ * phase with no plans; a phase that already has PLANs routes to execution,
1616
+ * and one whose plans all have SUMMARYs awaits verification — never back to
1617
+ * `/gsd-plan-phase`. Which phase is selected stays #4078's rule.
1618
+ */
1619
+ function nextPhaseWording(disk) {
1620
+ switch (disk?.status) {
1621
+ case 'PLANNED':
1622
+ return { status: 'Ready to execute', currentPlan: 'Not started', stoppedAt: 'ready to execute' };
1623
+ case 'IN_PROGRESS':
1624
+ return { status: 'Ready to execute', currentPlan: String(disk.summaryCount + 1), stoppedAt: 'ready to execute' };
1625
+ case 'EXECUTED':
1626
+ case 'NEEDS_REVIEW':
1627
+ return {
1628
+ status: 'Phase complete — ready for verification',
1629
+ currentPlan: String(disk.planCount),
1630
+ stoppedAt: 'ready to verify',
1631
+ };
1632
+ default:
1633
+ return { status: 'Ready to plan', currentPlan: 'Not started', stoppedAt: 'ready to plan' };
1634
+ }
1635
+ }
1581
1636
  /**
1582
1637
  * Apply a `completePhase` transition to STATE.md content.
1583
1638
  *
@@ -1585,7 +1640,9 @@ function advancePlanCore(content, deps) {
1585
1640
  * (phase.cts) onto the substrate. Owns the field-classification-governed body
1586
1641
  * mutations: Current Phase (preserving the `of total` shape and phase name),
1587
1642
  * Current Phase Name, Status (`All phases complete` on the last phase, else
1588
- * `Ready to plan` per ADR-2207), Current Plan (`Not started`), Last Activity + Description,
1643
+ * `Ready to plan` per ADR-2207 — or, #4824, the next phase's disk-derived
1644
+ * wording when it is already planned or executed), Current Plan (`Not started`
1645
+ * unless the next phase is in flight), Last Activity + Description,
1589
1646
  * and the Completed/Total Phases + Progress percent block (re-derived from the
1590
1647
  * roadmap via the injected `roadmapProvider`).
1591
1648
  *
@@ -1602,6 +1659,7 @@ function advancePlanCore(content, deps) {
1602
1659
  function completePhaseCore(content, intent, deps) {
1603
1660
  const updated = [];
1604
1661
  const today = deps.clock.localToday();
1662
+ const nextWording = nextPhaseWording(intent.isLastPhase ? null : intent.nextPhaseDisk ?? null);
1605
1663
  // Consult the field-classification table for the frontmatter keys this
1606
1664
  // transition touches (same guard beginPhaseCore applies). A missing row is a
1607
1665
  // substrate defect — fail loudly rather than silently re-encoding policy.
@@ -1665,7 +1723,7 @@ function completePhaseCore(content, intent, deps) {
1665
1723
  // Status — `All phases complete` on the final phase (ADR-2207), otherwise
1666
1724
  // `Ready to plan`. Milestone termination (`<version> milestone complete`) is
1667
1725
  // owned solely by the milestone-close verb (milestoneCompleteCore).
1668
- const statusValue = intent.isLastPhase ? 'All phases complete' : 'Ready to plan';
1726
+ const statusValue = intent.isLastPhase ? 'All phases complete' : nextWording.status;
1669
1727
  const statusAfter = (0, state_document_cjs_1.stateReplaceFieldWithFallback)(body, 'Status', null, statusValue);
1670
1728
  if (statusAfter !== body) {
1671
1729
  body = statusAfter;
@@ -1682,10 +1740,10 @@ function completePhaseCore(content, intent, deps) {
1682
1740
  let planAfter;
1683
1741
  const positionScope = (0, state_document_cjs_1.stateCurrentPositionSlice)(body);
1684
1742
  if (positionScope !== null) {
1685
- planAfter = (0, markdown_sectionizer_cjs_1.withSection)(body, (h) => (h.level === 2 || h.level === 3) && h.text.trim().toLowerCase() === 'current position', (sectionBody) => (0, state_document_cjs_1.stateReplaceFieldWithFallback)(sectionBody, 'Current Plan', 'Plan', 'Not started'), { levelBounded: true });
1743
+ planAfter = (0, markdown_sectionizer_cjs_1.withSection)(body, (h) => (h.level === 2 || h.level === 3) && h.text.trim().toLowerCase() === 'current position', (sectionBody) => (0, state_document_cjs_1.stateReplaceFieldWithFallback)(sectionBody, 'Current Plan', 'Plan', nextWording.currentPlan), { levelBounded: true });
1686
1744
  }
1687
1745
  else {
1688
- planAfter = (0, state_document_cjs_1.stateReplaceFieldWithFallback)(body, 'Current Plan', 'Plan', 'Not started');
1746
+ planAfter = (0, state_document_cjs_1.stateReplaceFieldWithFallback)(body, 'Current Plan', 'Plan', nextWording.currentPlan);
1689
1747
  }
1690
1748
  if (planAfter !== body) {
1691
1749
  body = planAfter;
@@ -1729,7 +1787,7 @@ function completePhaseCore(content, intent, deps) {
1729
1787
  // milestone termination wording stays owned by milestoneCompleteCore.
1730
1788
  const stoppedAtLine = intent.isLastPhase
1731
1789
  ? `Phase ${intent.phaseNum} complete — all phases complete`
1732
- : `Phase ${intent.phaseNum} complete${intent.nextPhaseNum ? `, ready to plan Phase ${intent.nextPhaseNum}` : ''}`;
1790
+ : `Phase ${intent.phaseNum} complete${intent.nextPhaseNum ? `, ${nextWording.stoppedAt} Phase ${intent.nextPhaseNum}` : ''}`;
1733
1791
  const stoppedAfter = (0, state_document_cjs_1.stateReplaceFieldInSession)(body, 'Stopped At', 'Stopped at', stoppedAtLine);
1734
1792
  if (stoppedAfter !== body) {
1735
1793
  body = stoppedAfter;