@opengsd/gsd-core 1.9.0 → 1.10.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +2 -3
- package/.opencode/plugins/gsd-core.js +8 -1
- package/agents/gsd-code-fixer.md +131 -34
- package/agents/gsd-debugger.md +12 -246
- package/agents/gsd-executor.md +7 -5
- package/agents/gsd-integration-checker.md +3 -0
- package/agents/gsd-plan-checker.md +9 -0
- package/agents/gsd-planner.md +5 -8
- package/agents/gsd-roadmapper.md +21 -3
- package/agents/gsd-verifier.md +14 -70
- package/bin/install.js +503 -341
- package/commands/gsd/mempalace-capture.md +1 -1
- package/commands/gsd/new-milestone.md +1 -1
- package/commands/gsd/plan-phase.md +1 -1
- package/gsd-core/bin/gsd-tools.cjs +607 -63
- package/gsd-core/bin/lib/active-workstream-store.cjs +25 -0
- package/gsd-core/bin/lib/agent-install-check.cjs +38 -6
- package/gsd-core/bin/lib/api-coverage.cjs +120 -0
- package/gsd-core/bin/lib/audit.cjs +89 -1
- package/gsd-core/bin/lib/broken-windows.cjs +36 -6
- package/gsd-core/bin/lib/capability-registry.cjs +96 -110
- package/gsd-core/bin/lib/capability-validator.cjs +12 -2
- package/gsd-core/bin/lib/check-command-router.cjs +43 -1
- package/gsd-core/bin/lib/command-aliases.cjs +72 -0
- package/gsd-core/bin/lib/commands.cjs +26 -25
- package/gsd-core/bin/lib/commonjs-marker.cjs +136 -0
- package/gsd-core/bin/lib/config-loader.cjs +1 -0
- package/gsd-core/bin/lib/config.cjs +12 -1
- package/gsd-core/bin/lib/context-composer.cjs +278 -0
- package/gsd-core/bin/lib/context-predicates.cjs +506 -0
- package/gsd-core/bin/lib/core-utils.cjs +91 -12
- package/gsd-core/bin/lib/docs.cjs +3 -2
- package/gsd-core/bin/lib/external-job.cjs +19 -4
- package/gsd-core/bin/lib/frontmatter.cjs +84 -12
- package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +57 -6
- package/gsd-core/bin/lib/git-base-branch.cjs +58 -15
- package/gsd-core/bin/lib/graphify.cjs +142 -27
- package/gsd-core/bin/lib/gsd2-import.cjs +27 -4
- package/gsd-core/bin/lib/host-integration.cjs +13 -1
- package/gsd-core/bin/lib/init-command-router.cjs +83 -8
- package/gsd-core/bin/lib/init.cjs +1021 -57
- package/gsd-core/bin/lib/install-engine.cjs +64 -10
- package/gsd-core/bin/lib/install-profiles.cjs +27 -1
- package/gsd-core/bin/lib/installer-migration-authoring.cjs +3 -1
- package/gsd-core/bin/lib/installer-migration-report.cjs +4 -0
- package/gsd-core/bin/lib/installer-migrations/007-retire-config-root-commonjs-marker.cjs +149 -0
- package/gsd-core/bin/lib/installer-migrations/008-cursor-retire-commands-surface.cjs +55 -0
- package/gsd-core/bin/lib/installer-migrations/009-pi-retire-reserved-hooks-dir.cjs +199 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +87 -1
- package/gsd-core/bin/lib/io.cjs +28 -3
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +6 -0
- package/gsd-core/bin/lib/mcp-catalog.cjs +518 -0
- package/gsd-core/bin/lib/mcp-server.cjs +135 -3
- package/gsd-core/bin/lib/milestone.cjs +106 -51
- package/gsd-core/bin/lib/phase-id.cjs +63 -0
- package/gsd-core/bin/lib/phase-locator.cjs +138 -45
- package/gsd-core/bin/lib/phase.cjs +260 -25
- package/gsd-core/bin/lib/plan-dependency-graph.cjs +232 -0
- package/gsd-core/bin/lib/planning-workspace.cjs +4 -0
- package/gsd-core/bin/lib/project-root.cjs +48 -0
- package/gsd-core/bin/lib/prompt-budget.cjs +128 -165
- package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +80 -0
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +99 -0
- package/gsd-core/bin/lib/review-lane-runner.cjs +30 -6
- package/gsd-core/bin/lib/roadmap-command-router.cjs +42 -9
- package/gsd-core/bin/lib/roadmap-parser.cjs +100 -18
- package/gsd-core/bin/lib/roadmap.cjs +37 -7
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +195 -62
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +15 -3
- package/gsd-core/bin/lib/runtime-homes.cjs +154 -41
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +105 -41
- package/gsd-core/bin/lib/section-manifest.cjs +209 -0
- package/gsd-core/bin/lib/shell-command-projection.cjs +113 -27
- package/gsd-core/bin/lib/smart-entry.cjs +12 -0
- package/gsd-core/bin/lib/state-transition.cjs +73 -8
- package/gsd-core/bin/lib/state.cjs +151 -62
- package/gsd-core/bin/lib/surface.cjs +12 -1
- package/gsd-core/bin/lib/uat-predicate.cjs +11 -1
- package/gsd-core/bin/lib/uat.cjs +320 -21
- package/gsd-core/bin/lib/unusable-input.cjs +9 -0
- package/gsd-core/bin/lib/verification.cjs +29 -12
- package/gsd-core/bin/lib/verify.cjs +29 -5
- package/gsd-core/bin/lib/workflow-fragments.cjs +557 -0
- package/gsd-core/bin/lib/workstream-inventory-builder.cjs +181 -18
- package/gsd-core/bin/lib/workstream-inventory.cjs +519 -27
- package/gsd-core/bin/lib/workstream.cjs +6 -0
- package/gsd-core/bin/lib/worktree-base-ref.cjs +50 -6
- package/gsd-core/bin/lib/worktree-safety.cjs +276 -118
- package/gsd-core/bin/shared/config-schema.manifest.json +2 -0
- package/gsd-core/references/artifact-types.md +10 -3
- package/gsd-core/references/autonomous-ui-design-contract.md +42 -0
- package/gsd-core/references/debugger-techniques.md +255 -0
- package/gsd-core/references/research-documentation-lookup.md +5 -3
- package/gsd-core/references/specless-probe-fallback.md +7 -6
- package/gsd-core/references/verifier-wiring-patterns.md +100 -0
- package/gsd-core/references/worktree-branch-check.md +2 -2
- package/gsd-core/templates/summary-complex.md +2 -0
- package/gsd-core/templates/summary-minimal.md +2 -0
- package/gsd-core/templates/summary-standard.md +2 -0
- package/gsd-core/templates/summary.md +2 -0
- package/gsd-core/workflows/audit-milestone.md +3 -0
- package/gsd-core/workflows/autonomous/steps/converge-banner.md +1 -0
- package/gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md +11 -0
- package/gsd-core/workflows/autonomous/steps/converge-dispatch-inline.md +7 -0
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +21 -0
- package/gsd-core/workflows/autonomous/steps/converge-loop.md +7 -0
- package/gsd-core/workflows/autonomous.md +32 -69
- package/gsd-core/workflows/code-review/steps/dispatch-fix.md +39 -0
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +83 -0
- package/gsd-core/workflows/code-review.md +42 -145
- package/gsd-core/workflows/complete-milestone/steps/git-tag.md +29 -0
- package/gsd-core/workflows/complete-milestone.md +23 -81
- package/gsd-core/workflows/debug.md +9 -12
- package/gsd-core/workflows/diagnose-issues.md +22 -0
- package/gsd-core/workflows/discovery-phase.md +4 -4
- package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +15 -0
- package/gsd-core/workflows/discuss-phase-assumptions.md +5 -16
- package/gsd-core/workflows/docs-update/steps/dispatch-monorepo-packages.md +51 -0
- package/gsd-core/workflows/docs-update.md +8 -51
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +34 -2
- package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +50 -0
- package/gsd-core/workflows/execute-phase/steps/partial-wave.md +31 -0
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +19 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +42 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate.md +43 -37
- package/gsd-core/workflows/execute-phase.md +65 -137
- package/gsd-core/workflows/execute-plan.md +1 -1
- package/gsd-core/workflows/help/modes/full.md +6 -1
- package/gsd-core/workflows/ingest-docs.md +2 -1
- package/gsd-core/workflows/new-milestone/steps/project-md-milestone-write.md +16 -0
- package/gsd-core/workflows/new-milestone/steps/reset-phase-safety.md +19 -0
- package/gsd-core/workflows/new-milestone.md +21 -38
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +176 -0
- package/gsd-core/workflows/new-project/steps/auto-mode-detection.md +32 -0
- package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +18 -0
- package/gsd-core/workflows/new-project.md +13 -226
- package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +15 -0
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +110 -0
- package/gsd-core/workflows/plan-phase/steps/prd-express-gate.md +8 -0
- package/gsd-core/workflows/plan-phase/steps/research-only-early-exit.md +17 -0
- package/gsd-core/workflows/plan-phase/steps/research-only-modifiers.md +16 -0
- package/gsd-core/workflows/plan-phase/steps/reviews-prerequisite.md +17 -0
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +149 -0
- package/gsd-core/workflows/plan-phase.md +49 -193
- package/gsd-core/workflows/progress/steps/forensic-audit.md +125 -0
- package/gsd-core/workflows/progress/steps/mvp-display.md +18 -0
- package/gsd-core/workflows/progress.md +11 -153
- package/gsd-core/workflows/quick/steps/discussion-phase.md +124 -0
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +111 -0
- package/gsd-core/workflows/quick/steps/quick-verification.md +46 -0
- package/gsd-core/workflows/quick/steps/research-phase.md +72 -0
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +37 -0
- package/gsd-core/workflows/quick.md +20 -390
- package/gsd-core/workflows/resume-project.md +3 -0
- package/gsd-core/workflows/review/steps/reviewer-instances-note-1.md +4 -0
- package/gsd-core/workflows/review/steps/reviewer-instances-note-2.md +3 -0
- package/gsd-core/workflows/review.md +15 -8
- package/gsd-core/workflows/section-manifest.json +219 -0
- package/gsd-core/workflows/sketch.md +1 -1
- package/gsd-core/workflows/spec-phase.md +17 -14
- package/gsd-core/workflows/spike-wrap-up.md +20 -5
- package/gsd-core/workflows/spike.md +50 -16
- package/gsd-core/workflows/sync-skills.md +49 -11
- package/gsd-core/workflows/transition/steps/workstream-collision-check.md +17 -0
- package/gsd-core/workflows/transition.md +8 -21
- package/gsd-core/workflows/ui-phase.md +8 -7
- package/gsd-core/workflows/update/steps/channel-banner.md +7 -0
- package/gsd-core/workflows/update.md +18 -7
- package/gsd-core/workflows/verify-phase.md +4 -7
- package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +36 -0
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +21 -0
- package/gsd-core/workflows/verify-work.md +8 -58
- package/hooks/dist/gsd-agent-isolation-guard.js +428 -0
- package/hooks/dist/gsd-check-update-worker.js +14 -5
- package/hooks/dist/gsd-cursor-subagent-start.js +532 -26
- package/hooks/dist/gsd-read-injection-scanner.js +7 -0
- package/hooks/dist/gsd-statusline.js +72 -6
- package/hooks/dist/gsd-worktree-path-guard.js +2 -1
- package/hooks/dist/gsd-write-guard.js +359 -0
- package/hooks/dist/lib/isolation-sentinel.js +268 -0
- package/hooks/dist/managed-hooks-registry.cjs +2 -0
- package/hooks/gsd-agent-isolation-guard.js +428 -0
- package/hooks/gsd-check-update-worker.js +14 -5
- package/hooks/gsd-cursor-subagent-start.js +532 -26
- package/hooks/gsd-read-injection-scanner.js +7 -0
- package/hooks/gsd-statusline.js +72 -6
- package/hooks/gsd-worktree-path-guard.js +2 -1
- package/hooks/gsd-write-guard.js +359 -0
- package/hooks/hooks.json +12 -0
- package/hooks/lib/isolation-sentinel.js +268 -0
- package/hooks/managed-hooks-registry.cjs +2 -0
- package/package.json +14 -5
- package/pi/gsd.cjs +57 -12
- package/scripts/build-hooks.js +9 -0
- package/scripts/changeset/lint.cjs +9 -2
- package/scripts/changeset/serialize.cjs +5 -1
- package/scripts/gen-capability-matrix.cjs +1 -1
- package/scripts/gen-context-index.cjs +448 -0
- package/scripts/gen-inventory-manifest.cjs +101 -1
- package/scripts/gen-prompt-budget-parity-corpus.cjs +645 -0
- package/scripts/gen-registry.cjs +39 -15
- package/scripts/gen-section-manifest.cjs +638 -0
- package/scripts/generate-package-identity.cjs +4 -2
- package/scripts/lint-allow-test-rule-refs.allowlist.json +17 -31
- package/scripts/lint-compiled-artifact-sync.cjs +6 -1
- package/scripts/lint-docs-command-form.cjs +195 -0
- package/scripts/lint-docs-required.cjs +9 -1
- package/scripts/lint-emitted-drift-ack.cjs +215 -20
- package/scripts/lint-example-parser-parity.cjs +395 -0
- package/scripts/lint-test-file-count.allowlist.json +27 -1
- package/scripts/mutation-matrix.cjs +13 -0
- package/scripts/prompt-injection-scan.sh +27 -6
- package/scripts/registry-schema.cjs +323 -94
- package/scripts/run-tests.cjs +3 -2
- package/scripts/validate-registry.cjs +10 -6
- package/skills/gsd-autonomous/SKILL.md +1 -1
- package/skills/gsd-execute-phase/SKILL.md +1 -1
- package/skills/gsd-mempalace-capture/SKILL.md +1 -1
- package/skills/gsd-new-milestone/SKILL.md +1 -1
- package/skills/gsd-plan-phase/SKILL.md +2 -2
- package/vscode/package.json +1 -1
- package/scripts/gen-emitted-baseline.cjs +0 -145
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// hooks/lib/isolation-sentinel.js — shared sentinel reader for the #3045
|
|
3
|
+
// agent-dispatch isolation guards (hooks/gsd-agent-isolation-guard.js,
|
|
4
|
+
// hooks/gsd-cursor-subagent-start.js).
|
|
5
|
+
//
|
|
6
|
+
// #3045 BLOCKER: the guards previously keyed enforcement on the capability
|
|
7
|
+
// REGISTRY's `dispatch.isolation` ("this host CAN isolate"), not the
|
|
8
|
+
// workflow's resolved per-dispatch ISOLATION ("this dispatch SHOULD be
|
|
9
|
+
// isolated"). Sequential ISOLATION=none legitimately happens on a
|
|
10
|
+
// harness-worktree-capable host — project-level `workflow.use_worktrees:
|
|
11
|
+
// false`, the #2474 per-plan submodule degrade, and the #683/#3060
|
|
12
|
+
// base-check auto-degrade all resolve to `none` and are NOT bugs
|
|
13
|
+
// (gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md:
|
|
14
|
+
// "Sequential mode … Omit isolation=\"worktree\" from the Agent call").
|
|
15
|
+
//
|
|
16
|
+
// The workflow already computes ISOLATION deterministically in shell before
|
|
17
|
+
// any executor dispatch. CORE REDESIGN (two-review follow-up): the PRIMARY
|
|
18
|
+
// write path is now `dispatch-isolation` itself (gsd-tools.cjs
|
|
19
|
+
// routeDispatchIsolation) — it persists mode + harnessFlag + phase/plan
|
|
20
|
+
// identifiers to the sentinel as an unconditional side effect of resolving
|
|
21
|
+
// them, since the workflow must call it to learn ISOLATION at all.
|
|
22
|
+
// `record-dispatch-isolation` (routeRecordDispatchIsolation) remains as an
|
|
23
|
+
// explicit fallback for the per-plan submodule-degrade override and shares
|
|
24
|
+
// the exact same atomic-write implementation.
|
|
25
|
+
//
|
|
26
|
+
// Sentinel path: `<cwd>/.gsd/dispatch-isolation-sentinel.json`. `.gsd` is
|
|
27
|
+
// gitignored (root `.gitignore`'s bare `.gsd` entry matches at any depth),
|
|
28
|
+
// and lives inside the checkout that ran the workflow — concurrent worktrees
|
|
29
|
+
// of the same repo are separate directories on disk, so each gets its own
|
|
30
|
+
// sentinel with no cross-worktree collision, no lock file needed.
|
|
31
|
+
//
|
|
32
|
+
// #3045 SECURITY F3 (LOW, accepted risk — documented, not redesigned for):
|
|
33
|
+
// the sentinel lives at a fixed, predictable, gitignored path inside the
|
|
34
|
+
// PRIMARY checkout. An agent already running inside a legitimately isolated
|
|
35
|
+
// worktree (i.e. one that has shell/filesystem access to the primary
|
|
36
|
+
// checkout's absolute path, which every worktree-isolated agent does by
|
|
37
|
+
// construction) could write a fabricated fresh `{isolation:"none"}` sentinel
|
|
38
|
+
// into the primary checkout to silence this guard for that checkout's next
|
|
39
|
+
// dispatch.
|
|
40
|
+
// - This grants NO NEW write privilege: that same agent could already edit
|
|
41
|
+
// any file in the primary checkout directly (worktree isolation protects
|
|
42
|
+
// the primary checkout from an UNTRUSTED executor's own dispatch, not
|
|
43
|
+
// from a trusted orchestrator process choosing to reach across).
|
|
44
|
+
// - It DOES add an evasion path: silencing the guard via the sentinel
|
|
45
|
+
// leaves no trace in `git status` (`.gsd/` is gitignored), where a direct
|
|
46
|
+
// edit to a tracked file would.
|
|
47
|
+
// Accepted because the threat model this guard defends against is an
|
|
48
|
+
// UNCONSENTED, UNVERIFIED dispatch — not a deliberately adversarial
|
|
49
|
+
// orchestrator process, which already has strictly more direct means to
|
|
50
|
+
// cause harm than forging this one file. If that threat model changes (e.g.
|
|
51
|
+
// executors become mutually distrusting / sandboxed from the orchestrator's
|
|
52
|
+
// own filesystem), the hardening path is a SESSION-KEYED sentinel written
|
|
53
|
+
// outside any worktree the executor can reach (e.g. under the harness's own
|
|
54
|
+
// config dir, keyed by a session/run id neither the executor nor a forged
|
|
55
|
+
// file can predict) rather than a path derivable from `cwd`.
|
|
56
|
+
|
|
57
|
+
const fs = require('fs');
|
|
58
|
+
const path = require('path');
|
|
59
|
+
|
|
60
|
+
// Isolation modes ADR-1239 declares (mirrors gsd-tools.cjs
|
|
61
|
+
// routeDispatchIsolation / routeRecordDispatchIsolation).
|
|
62
|
+
const VALID_ISOLATION = new Set(['harness-worktree', 'orchestrator-worktree', 'none']);
|
|
63
|
+
|
|
64
|
+
const SENTINEL_RELATIVE_PATH = path.join('.gsd', 'dispatch-isolation-sentinel.json');
|
|
65
|
+
|
|
66
|
+
// #3045 SECURITY F2 fix: how long a written sentinel is trusted as "this
|
|
67
|
+
// dispatch's decision" before a reader falls back to the conservative
|
|
68
|
+
// registry+config check.
|
|
69
|
+
//
|
|
70
|
+
// Previously 4h, on the theory that a slow multi-wave phase execution could
|
|
71
|
+
// span well over an hour. That reasoning no longer holds: the #3045 CORE
|
|
72
|
+
// REDESIGN makes `dispatch-isolation` (gsd-tools.cjs routeDispatchIsolation)
|
|
73
|
+
// the sole write path, called as a side effect of resolving ISOLATION — and
|
|
74
|
+
// the workflow now re-resolves (and therefore re-records) immediately before
|
|
75
|
+
// EVERY plan's dispatch, at the per-plan worktree gate
|
|
76
|
+
// (execute-phase/steps/per-plan-worktree-gate.md), not once per phase. A long
|
|
77
|
+
// trust window no longer buys the workflow anything and only widens the
|
|
78
|
+
// window in which a stale sentinel from an EARLIER, DIFFERENT phase/plan
|
|
79
|
+
// (e.g. one that legitimately degraded to `none`) could be misread as
|
|
80
|
+
// authorizing a LATER dispatch that never got its own fresh record (a model
|
|
81
|
+
// skipping the kwarg on a harness-worktree phase while a same-session
|
|
82
|
+
// same-project stale `none` from a prior phase is still "fresh" by the old
|
|
83
|
+
// 4h window).
|
|
84
|
+
//
|
|
85
|
+
// 10 minutes generously covers the real latency between a per-plan gate's
|
|
86
|
+
// resolve call and that same plan's `Agent()`/`Task()` dispatch (worktree
|
|
87
|
+
// creation, orphan-worktree sweep, base-check, prompt composition) — all
|
|
88
|
+
// bounded, sub-minute operations per their own repo-mandated subprocess
|
|
89
|
+
// timeouts — while being far too short for a sentinel to survive into a
|
|
90
|
+
// later, unrelated phase.
|
|
91
|
+
const SENTINEL_STALE_MS = 10 * 60 * 1000; // 10 minutes
|
|
92
|
+
|
|
93
|
+
function sentinelPath(cwd) {
|
|
94
|
+
return path.join(cwd, SENTINEL_RELATIVE_PATH);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Resolve the project root a sentinel should be read from/written to, using
|
|
99
|
+
* the SAME derivation gsd-tools.cjs's dispatcher applies to every `--cwd`
|
|
100
|
+
* before invoking a route handler: `findProjectRoot(resolveMainWorktreeCwd(cwd))`
|
|
101
|
+
* (gsd-core/bin/gsd-tools.cjs main(), :3506/:3603 — `record-dispatch-isolation`
|
|
102
|
+
* and `dispatch-isolation` are not in SKIP_ROOT_RESOLUTION, so every write
|
|
103
|
+
* goes through both steps).
|
|
104
|
+
*
|
|
105
|
+
* #3045 MINOR fix: the guard hooks previously read the sentinel from the raw
|
|
106
|
+
* `data.cwd` / `workspace_roots[i]` the harness reports, with NO equivalent
|
|
107
|
+
* resolution. For a linked worktree that does not itself own a `.planning/`
|
|
108
|
+
* (the common shape — `.planning/` lives in the main worktree only), the
|
|
109
|
+
* writer resolves up to the MAIN worktree and writes there, while the reader
|
|
110
|
+
* checked `.planning/config.json` at the raw (unresolved) linked-worktree
|
|
111
|
+
* path, found nothing, and silently treated the dispatch as "not a GSD
|
|
112
|
+
* project" (inert allow) — the guard was reading a sentinel that was never
|
|
113
|
+
* written where it looked. Deriving both sides through this one function
|
|
114
|
+
* closes that divergence.
|
|
115
|
+
*
|
|
116
|
+
* `findProjectRoot`/`resolveWorktreeRoot` are read from the sibling
|
|
117
|
+
* `gsd-core/bin/lib/*.cjs` modules staged alongside these hooks at install
|
|
118
|
+
* time (same pattern the guard hooks already use for
|
|
119
|
+
* capability-registry.cjs/runtime-name-policy.cjs) — two directories up from
|
|
120
|
+
* `hooks/lib/` (`hooks/lib/isolation-sentinel.js` -> `hooks/` -> repo/install
|
|
121
|
+
* root -> `gsd-core/bin/lib/`), mirroring the one-directory-up requires the
|
|
122
|
+
* top-level `hooks/*.js` guard scripts already use successfully.
|
|
123
|
+
*
|
|
124
|
+
* Never throws; any resolution failure (module missing, git unavailable,
|
|
125
|
+
* git timeout) degrades to the raw `cwd` unchanged — the caller's existing
|
|
126
|
+
* "sentinel absent -> conservative fallback" path already covers that safely.
|
|
127
|
+
*/
|
|
128
|
+
function resolveSentinelRoot(cwd) {
|
|
129
|
+
try {
|
|
130
|
+
if (fs.existsSync(path.join(cwd, '.planning'))) {
|
|
131
|
+
return cwd;
|
|
132
|
+
}
|
|
133
|
+
const { resolveWorktreeRoot } = require('../../gsd-core/bin/lib/worktree-safety.cjs');
|
|
134
|
+
const { root } = resolveWorktreeRoot(cwd);
|
|
135
|
+
const { findProjectRoot } = require('../../gsd-core/bin/lib/project-root.cjs');
|
|
136
|
+
return findProjectRoot(root);
|
|
137
|
+
} catch {
|
|
138
|
+
return cwd;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Read and validate the dispatch-isolation sentinel for `cwd`. Never throws.
|
|
144
|
+
* `cwd` is resolved through `resolveSentinelRoot` first (#3045 MINOR — see
|
|
145
|
+
* its doc comment), so callers may pass the raw, unresolved dispatch cwd
|
|
146
|
+
* directly.
|
|
147
|
+
*
|
|
148
|
+
* Returns one of:
|
|
149
|
+
* { present: false }
|
|
150
|
+
* { present: true, stale: true, malformed: true }
|
|
151
|
+
* { present: true, stale: true, malformed: false, isolation, harnessFlag, phase, plan, writtenAt }
|
|
152
|
+
* { present: true, stale: false, malformed: false, isolation, harnessFlag, phase, plan, writtenAt }
|
|
153
|
+
*
|
|
154
|
+
* A malformed/unparseable sentinel is treated as STALE, never fatal — the
|
|
155
|
+
* caller's conservative fallback path covers both "absent" and "stale"
|
|
156
|
+
* identically.
|
|
157
|
+
*
|
|
158
|
+
* `clock` is injectable (`{ now(): number }`, defaults to the real `Date`)
|
|
159
|
+
* per the repo's clock-seam convention, so staleness is testable without
|
|
160
|
+
* asserting on wall-clock time.
|
|
161
|
+
*/
|
|
162
|
+
function readSentinel(cwd, { clock = Date } = {}) {
|
|
163
|
+
const root = resolveSentinelRoot(cwd);
|
|
164
|
+
let raw;
|
|
165
|
+
try {
|
|
166
|
+
raw = fs.readFileSync(sentinelPath(root), 'utf-8');
|
|
167
|
+
} catch {
|
|
168
|
+
return { present: false };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
let parsed;
|
|
172
|
+
try {
|
|
173
|
+
parsed = JSON.parse(raw);
|
|
174
|
+
} catch {
|
|
175
|
+
return { present: true, stale: true, malformed: true };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (
|
|
179
|
+
!parsed || typeof parsed !== 'object' ||
|
|
180
|
+
!VALID_ISOLATION.has(parsed.isolation) ||
|
|
181
|
+
typeof parsed.written_at !== 'number' || !Number.isFinite(parsed.written_at)
|
|
182
|
+
) {
|
|
183
|
+
return { present: true, stale: true, malformed: true };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const harnessFlag = typeof parsed.harness_flag === 'string' && parsed.harness_flag.length > 0
|
|
187
|
+
? parsed.harness_flag
|
|
188
|
+
: null;
|
|
189
|
+
const phase = typeof parsed.phase === 'string' && parsed.phase.length > 0 ? parsed.phase : null;
|
|
190
|
+
// #3045 SECURITY F2: `plan` was not previously part of the sentinel shape.
|
|
191
|
+
// Recorded so a phase-level-only sentinel (plan: null) is distinguishable
|
|
192
|
+
// from a plan-scoped one — see the guards' dispatch-matching logic, which
|
|
193
|
+
// treats a plan/phase MISMATCH (both sides present and disagreeing) as "no
|
|
194
|
+
// applicable sentinel", not an allow.
|
|
195
|
+
const plan = typeof parsed.plan === 'string' && parsed.plan.length > 0 ? parsed.plan : null;
|
|
196
|
+
|
|
197
|
+
const now = clock.now();
|
|
198
|
+
const age = now - parsed.written_at;
|
|
199
|
+
// Negative age beyond a small tolerance means the sentinel claims to be
|
|
200
|
+
// written in the future — never trust it, but still surface the parsed
|
|
201
|
+
// fields so callers can log an actionable reason.
|
|
202
|
+
const stale = age >= SENTINEL_STALE_MS || age < -5000;
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
present: true,
|
|
206
|
+
stale,
|
|
207
|
+
malformed: false,
|
|
208
|
+
isolation: parsed.isolation,
|
|
209
|
+
harnessFlag,
|
|
210
|
+
phase,
|
|
211
|
+
plan,
|
|
212
|
+
writtenAt: parsed.written_at,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* #3045 SECURITY F2: extract the `{plan, phase}` a specific Agent()/Task()
|
|
218
|
+
* dispatch is FOR, from the one place that data is reliably embedded today —
|
|
219
|
+
* the dispatch prompt/description text (`execute-phase.md`'s Agent() block
|
|
220
|
+
* uses the literal shape `description="Execute plan {plan_number} of phase
|
|
221
|
+
* {phase_number}"`, and the prompt body's `<objective>` repeats "Execute plan
|
|
222
|
+
* {plan_number} of phase {phase_number}-{phase_name}." verbatim — the SAME
|
|
223
|
+
* text the orchestrator-worktree EXECUTOR_PROMPT template and Cursor's `task`
|
|
224
|
+
* field carry, since Cursor dispatches the same prompt content). There is no
|
|
225
|
+
* structured per-dispatch kwarg carrying plan/phase identifiers today (#3045
|
|
226
|
+
* would need a larger dispatch-protocol change to add one) — this is
|
|
227
|
+
* therefore a best-effort, NOT a guaranteed, extraction: a dispatch whose
|
|
228
|
+
* text doesn't match the expected shape returns `{ plan: null, phase: null }`
|
|
229
|
+
* and the caller must NOT treat that as a mismatch (see
|
|
230
|
+
* `sentinelAppliesToDispatch`).
|
|
231
|
+
*/
|
|
232
|
+
function extractDispatchIdentifiers(text) {
|
|
233
|
+
if (typeof text !== 'string' || text.length === 0) return { plan: null, phase: null };
|
|
234
|
+
const m = /execute\s+plan\s+(\S+)\s+of\s+phase\s+(\S+)/i.exec(text);
|
|
235
|
+
if (!m) return { plan: null, phase: null };
|
|
236
|
+
return { plan: m[1], phase: m[2] };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* #3045 SECURITY F2: does a fresh, non-malformed sentinel apply to THIS
|
|
241
|
+
* dispatch? `dispatchIds` is the `{plan, phase}` extracted from the
|
|
242
|
+
* dispatch's own text via `extractDispatchIdentifiers` (or manually supplied
|
|
243
|
+
* by a caller with a more reliable source).
|
|
244
|
+
*
|
|
245
|
+
* Returns false (mismatch — "no applicable sentinel") ONLY when both sides
|
|
246
|
+
* carry a value for the SAME identifier and they disagree. Any side missing
|
|
247
|
+
* a value (sentinel predates this fix, or the dispatch text didn't match the
|
|
248
|
+
* expected shape) is treated as "cannot compare" and does NOT itself produce
|
|
249
|
+
* a mismatch — this stays a defense-in-depth narrowing of an otherwise-fresh
|
|
250
|
+
* sentinel's applicability, not a new fail-open/fail-closed axis on its own.
|
|
251
|
+
*/
|
|
252
|
+
function sentinelAppliesToDispatch(sentinel, dispatchIds) {
|
|
253
|
+
if (!sentinel || !dispatchIds) return true;
|
|
254
|
+
if (sentinel.phase && dispatchIds.phase && sentinel.phase !== dispatchIds.phase) return false;
|
|
255
|
+
if (sentinel.plan && dispatchIds.plan && sentinel.plan !== dispatchIds.plan) return false;
|
|
256
|
+
return true;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
module.exports = {
|
|
260
|
+
VALID_ISOLATION,
|
|
261
|
+
SENTINEL_RELATIVE_PATH,
|
|
262
|
+
SENTINEL_STALE_MS,
|
|
263
|
+
sentinelPath,
|
|
264
|
+
resolveSentinelRoot,
|
|
265
|
+
readSentinel,
|
|
266
|
+
extractDispatchIdentifiers,
|
|
267
|
+
sentinelAppliesToDispatch,
|
|
268
|
+
};
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* stale warnings for users who haven't cleaned up manually (#1750).
|
|
17
17
|
*/
|
|
18
18
|
const MANAGED_HOOKS = [
|
|
19
|
+
'gsd-agent-isolation-guard.js',
|
|
19
20
|
'gsd-check-update-worker.js',
|
|
20
21
|
'gsd-check-update.js',
|
|
21
22
|
'gsd-config-reload.js',
|
|
@@ -40,6 +41,7 @@ const MANAGED_HOOKS = [
|
|
|
40
41
|
'gsd-windsurf-pre-write.js',
|
|
41
42
|
'gsd-workflow-guard.js',
|
|
42
43
|
'gsd-worktree-path-guard.js',
|
|
44
|
+
'gsd-write-guard.js',
|
|
43
45
|
];
|
|
44
46
|
|
|
45
47
|
module.exports = { MANAGED_HOOKS };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengsd/gsd-core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
4
4
|
"description": "GSD Core is a meta-prompting, context engineering, and spec-driven development system for AI coding agents.",
|
|
5
5
|
"main": ".opencode/plugins/gsd-core.js",
|
|
6
6
|
"bin": {
|
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
"GEMINI.md",
|
|
22
22
|
"hooks",
|
|
23
23
|
"scripts",
|
|
24
|
+
"!scripts/gen-emitted-baseline.cjs",
|
|
25
|
+
"!scripts/qa-smell-ratchet.cjs",
|
|
24
26
|
"pi",
|
|
25
27
|
"vscode"
|
|
26
28
|
],
|
|
@@ -84,16 +86,18 @@
|
|
|
84
86
|
"check:identity-drift": "node scripts/lint-package-identity-drift.cjs",
|
|
85
87
|
"check:phase-id-drift": "node scripts/lint-phase-id-drift.cjs",
|
|
86
88
|
"check:integrity": "node scripts/check-npm-integrity.cjs",
|
|
87
|
-
"build": "npm run generate:identity && npm run build:lib && npm run gen:plugin-skills && npm run gen:loop-host-contract && npm run gen:capability-registry && npm run build:hooks",
|
|
89
|
+
"build": "npm run generate:identity && npm run build:lib && npm run gen:section-manifest && npm run gen:context-index && npm run gen:plugin-skills && npm run gen:loop-host-contract && npm run gen:capability-registry && npm run build:hooks",
|
|
88
90
|
"build:hooks": "node scripts/build-hooks.js",
|
|
89
91
|
"build:lib": "tsc -p tsconfig.build.json",
|
|
90
92
|
"generate:identity": "node scripts/generate-package-identity.cjs",
|
|
93
|
+
"gen:context-index": "node scripts/gen-context-index.cjs --write",
|
|
91
94
|
"gen:loop-host-contract": "node scripts/gen-loop-host-contract.cjs --write",
|
|
92
95
|
"gen:plugin-skills": "node scripts/gen-plugin-skills.cjs --write",
|
|
93
96
|
"gen:capability-registry": "node scripts/gen-capability-registry.cjs --write",
|
|
94
97
|
"gen:registry": "node scripts/gen-registry.cjs --write",
|
|
95
98
|
"gen:install-tree": "node scripts/gen-install-tree-fixtures.cjs",
|
|
96
|
-
"
|
|
99
|
+
"gen:section-manifest": "node scripts/gen-section-manifest.cjs --write",
|
|
100
|
+
"regen:derived": "npm run build && npm run gen:registry && node scripts/gen-adr-index.cjs --write && node scripts/gen-capability-matrix.cjs --write && node scripts/gen-inventory-manifest.cjs --write && node scripts/gen-context-index.cjs --write && npm run gen:section-manifest && node scripts/sync-manifest-versions.cjs && npm run gen:install-tree",
|
|
97
101
|
"validate:registry": "node scripts/validate-registry.cjs",
|
|
98
102
|
"prepack": "npm run build:lib",
|
|
99
103
|
"prepare": "npm run build:lib",
|
|
@@ -104,7 +108,7 @@
|
|
|
104
108
|
"lint": "eslint . --cache --cache-location node_modules/.cache/eslint/",
|
|
105
109
|
"lint:fix": "eslint . --fix",
|
|
106
110
|
"lint:table-schema-drift": "node scripts/lint-table-schema-drift.cjs",
|
|
107
|
-
"lint:ci": "npm run lint && npm run lint:skill-deps && npm run lint:generated-sync && node scripts/lint-test-file-count.cjs && node scripts/lint-command-contract.cjs && node scripts/lint-pr-check-project-dir.cjs && npm run lint:legacy-name && node scripts/lint-regression-test-names.cjs && node scripts/lint-allow-test-rule-refs.cjs && node scripts/lint-resolution-provenance.cjs && node scripts/lint-emitted-drift-ack.cjs && node scripts/lint-portable-timeout.cjs && node scripts/validate-registry.cjs && node scripts/lint-table-schema-drift.cjs && node scripts/lint-fix-has-regression-test.cjs",
|
|
111
|
+
"lint:ci": "npm run lint && npm run lint:skill-deps && npm run lint:generated-sync && node scripts/lint-test-file-count.cjs && node scripts/lint-command-contract.cjs && node scripts/lint-pr-check-project-dir.cjs && npm run lint:legacy-name && node scripts/lint-regression-test-names.cjs && node scripts/lint-allow-test-rule-refs.cjs && node scripts/lint-resolution-provenance.cjs && node scripts/lint-emitted-drift-ack.cjs && node scripts/lint-portable-timeout.cjs && node scripts/validate-registry.cjs && node scripts/lint-table-schema-drift.cjs && node scripts/lint-fix-has-regression-test.cjs && node scripts/lint-example-parser-parity.cjs && node scripts/lint-docs-command-form.cjs",
|
|
108
112
|
"lint:allow-test-rule-refs": "node scripts/lint-allow-test-rule-refs.cjs",
|
|
109
113
|
"lint:regression-names": "node scripts/lint-regression-test-names.cjs",
|
|
110
114
|
"lint:descriptions": "node scripts/lint-descriptions.cjs",
|
|
@@ -112,9 +116,11 @@
|
|
|
112
116
|
"lint:test-file-count": "node scripts/lint-test-file-count.cjs",
|
|
113
117
|
"lint:pr-checks": "node scripts/lint-pr-check-project-dir.cjs",
|
|
114
118
|
"lint:changeset": "node scripts/changeset/lint.cjs",
|
|
115
|
-
"lint:generated-sync": "node scripts/gen-capability-registry.cjs --check && node scripts/gen-loop-host-contract.cjs --check && node scripts/gen-capability-matrix.cjs --check && node scripts/sync-manifest-versions.cjs --check && node scripts/gen-inventory-manifest.cjs --check && node scripts/generate-package-identity.cjs --check && node scripts/gen-plugin-skills.cjs --check && node scripts/gen-registry.cjs --check && node scripts/gen-adr-index.cjs --check && node scripts/check-glossary-refs.cjs --check && node scripts/lint-compiled-artifact-sync.cjs --check",
|
|
119
|
+
"lint:generated-sync": "node scripts/gen-capability-registry.cjs --check && node scripts/gen-loop-host-contract.cjs --check && node scripts/gen-capability-matrix.cjs --check && node scripts/sync-manifest-versions.cjs --check && node scripts/gen-inventory-manifest.cjs --check && node scripts/generate-package-identity.cjs --check && node scripts/gen-plugin-skills.cjs --check && node scripts/gen-registry.cjs --check && node scripts/gen-adr-index.cjs --check && node scripts/check-glossary-refs.cjs --check && node scripts/lint-compiled-artifact-sync.cjs --check && node scripts/gen-context-index.cjs --check && node scripts/gen-section-manifest.cjs --check",
|
|
116
120
|
"lint:docs": "node scripts/lint-docs-required.cjs",
|
|
121
|
+
"lint:qa-smells": "node scripts/qa-smell-ratchet.cjs",
|
|
117
122
|
"lint:legacy-name": "node scripts/lint-legacy-dir-name.cjs",
|
|
123
|
+
"lint:docs-command-form": "node scripts/lint-docs-command-form.cjs",
|
|
118
124
|
"ci:test-scope": "node scripts/ci-test-scope.cjs",
|
|
119
125
|
"changeset": "node scripts/changeset/new.cjs",
|
|
120
126
|
"changelog:render": "node scripts/changeset/cli.cjs render",
|
|
@@ -124,10 +130,13 @@
|
|
|
124
130
|
"test:install": "node scripts/run-tests.cjs --suite install",
|
|
125
131
|
"test:security": "node scripts/run-tests.cjs --suite security",
|
|
126
132
|
"test:slow": "node scripts/run-tests.cjs --suite slow",
|
|
133
|
+
"test:qa": "node scripts/run-tests.cjs --suite qa",
|
|
127
134
|
"test:affected": "node scripts/run-affected-tests.cjs",
|
|
128
135
|
"test:coverage": "c8 --check-coverage --lines 70 --branches 60 --reporter text --include 'gsd-core/bin/lib/*.cjs' --exclude 'tests/**' --all node scripts/run-tests.cjs",
|
|
129
136
|
"test:coverage:scripts-floor": "c8 check-coverage --lines 55 --include 'scripts/**/*.cjs' --exclude 'tests/**' --all",
|
|
130
137
|
"test:coverage:unit": "c8 --reporter text --reporter json-summary --include 'gsd-core/bin/lib/*.cjs' --exclude 'tests/**' --all node scripts/run-tests.cjs --suite unit && node scripts/check-coverage-gate.cjs",
|
|
138
|
+
"test:coverage:unit:raw": "c8 --reporter none node scripts/run-tests.cjs --suite unit",
|
|
139
|
+
"test:coverage:report": "c8 report --reporter text --reporter json-summary --include 'gsd-core/bin/lib/*.cjs' --exclude 'tests/**' --all && node scripts/check-coverage-gate.cjs",
|
|
131
140
|
"test:coverage:all": "npm run test:coverage",
|
|
132
141
|
"test:mutation": "stryker run",
|
|
133
142
|
"test:mutation:since": "stryker run --incremental --since origin/next"
|
package/pi/gsd.cjs
CHANGED
|
@@ -63,6 +63,43 @@ function resolveEngineRoot(startDir) {
|
|
|
63
63
|
const ENGINE_ROOT = resolveEngineRoot(__dirname);
|
|
64
64
|
const GSD_CORE = path.join(ENGINE_ROOT, 'gsd-core');
|
|
65
65
|
|
|
66
|
+
// #3023: pi reserves `hooks/` as its (now deprecated) extension directory and
|
|
67
|
+
// warns on every startup when one exists, so the installer stages GSD's shared
|
|
68
|
+
// hook bundle under `gsd-hooks/` for pi. Probe in preference order rather than
|
|
69
|
+
// hardcoding either name: an installed pi tree has `gsd-hooks/`, a dev checkout
|
|
70
|
+
// has the repo-root `hooks/`, and a half-upgraded tree can transiently have both
|
|
71
|
+
// — in which case the renamed bundle is the current one and wins.
|
|
72
|
+
const SHARED_HOOKS_DIR_CANDIDATES = ['gsd-hooks', 'hooks'];
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Absolute path to the staged shared-hook bundle, or null when none is present.
|
|
76
|
+
* Never throws — a stat failure on any candidate is treated as "not this one".
|
|
77
|
+
*
|
|
78
|
+
* A candidate qualifies only when it is a directory AND non-empty. An install
|
|
79
|
+
* can be interrupted between `mkdirSync(gsd-hooks)` and the file copy, leaving
|
|
80
|
+
* a directory that exists but is empty; `gsd-hooks` is probed FIRST (it is the
|
|
81
|
+
* preferred, current name), so an empty `gsd-hooks/` would otherwise win over
|
|
82
|
+
* a fully-staged legacy `hooks/` and every hook would silently no-op —
|
|
83
|
+
* `runHook`'s `fs.existsSync(hookPath)` guard degrades per-file, so binding to
|
|
84
|
+
* an empty bundle produces no error at all, just silent inaction. A
|
|
85
|
+
* partially-staged bundle must lose to a fully-staged one for that reason.
|
|
86
|
+
* @param {string} engineRoot
|
|
87
|
+
* @returns {string|null}
|
|
88
|
+
*/
|
|
89
|
+
function resolveSharedHooksDir(engineRoot) {
|
|
90
|
+
for (const name of SHARED_HOOKS_DIR_CANDIDATES) {
|
|
91
|
+
const candidate = path.join(engineRoot, name);
|
|
92
|
+
try {
|
|
93
|
+
if (!fs.statSync(candidate).isDirectory()) continue;
|
|
94
|
+
if (fs.readdirSync(candidate).length === 0) continue;
|
|
95
|
+
return candidate;
|
|
96
|
+
} catch { /* absent or unreadable — try the next candidate */ }
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const SHARED_HOOKS_DIR = resolveSharedHooksDir(ENGINE_ROOT);
|
|
102
|
+
|
|
66
103
|
// ── curated top-level command families (gsd-tools.cjs TOP_LEVEL_USAGE) ──────
|
|
67
104
|
// readCmdNames() (scripts/fix-slash-commands.cjs) reads commands/, which pi
|
|
68
105
|
// does NOT install (it ships a single native-extension file, no shared
|
|
@@ -95,8 +132,8 @@ function getArgumentCompletions(prefix) {
|
|
|
95
132
|
/**
|
|
96
133
|
* Tokenize the raw `/gsd <args>` string into { family, subcommand, args }.
|
|
97
134
|
* Reuses the quote-aware whitespace tokenizer already shipped for hooks
|
|
98
|
-
* (
|
|
99
|
-
* splitting a second time. #2102 Stage 2: pi's capability descriptor no
|
|
135
|
+
* (the staged shared-hook bundle's `lib/git-cmd.js`'s `tokenize`) rather than
|
|
136
|
+
* re-implementing shell-word splitting a second time. #2102 Stage 2: pi's capability descriptor no
|
|
100
137
|
* longer sets `hostBehaviors.skipSharedHooksInstall` (adversarial-review
|
|
101
138
|
* finding #1/#2 — pi ships NO hooks/ with that flag set, so this require was
|
|
102
139
|
* dead in a real install), so the shared hooks/ bundle — including
|
|
@@ -112,7 +149,8 @@ function getArgumentCompletions(prefix) {
|
|
|
112
149
|
function parseGsdCommandArgs(rawArgs) {
|
|
113
150
|
let tokenize;
|
|
114
151
|
try {
|
|
115
|
-
(
|
|
152
|
+
if (!SHARED_HOOKS_DIR) throw new Error('no staged hook bundle');
|
|
153
|
+
({ tokenize } = require(path.join(SHARED_HOOKS_DIR, 'lib', 'git-cmd.js')));
|
|
116
154
|
} catch {
|
|
117
155
|
tokenize = (s) => String(s || '').split(/\s+/).filter(Boolean);
|
|
118
156
|
}
|
|
@@ -148,11 +186,10 @@ function buildGsdInvokeParameters() {
|
|
|
148
186
|
});
|
|
149
187
|
}
|
|
150
188
|
} catch {
|
|
151
|
-
// typebox is not installed in this environment — fall through
|
|
189
|
+
// typebox is not installed in this environment — fall through to the
|
|
190
|
+
// plain JSON-Schema fallback below. This is the normal path (typebox is
|
|
191
|
+
// NOT a gsd-core dependency), so no warning is emitted (#3022).
|
|
152
192
|
}
|
|
153
|
-
process.stderr.write(
|
|
154
|
-
'gsd: typebox unavailable — gsd_invoke "parameters" falling back to a plain JSON-schema object.\n',
|
|
155
|
-
);
|
|
156
193
|
return {
|
|
157
194
|
type: 'object',
|
|
158
195
|
properties: {
|
|
@@ -245,13 +282,14 @@ function buildBeforeProviderRequestHandler({ tier = 'sonnet' } = {}) {
|
|
|
245
282
|
* `node <hooks/hookFile>` with the payload piped to stdin, on a bounded
|
|
246
283
|
* timeout. NEVER throws — a missing hook file, a spawn error, or a timeout
|
|
247
284
|
* all degrade to a silent-allow result so a hook problem can never block pi.
|
|
248
|
-
* @param {string} hookFile filename under
|
|
285
|
+
* @param {string} hookFile filename under the resolved shared-hook bundle, e.g. "gsd-context-monitor.js"
|
|
249
286
|
* @param {object} payload
|
|
250
287
|
* @param {{ timeout?: number, cwd?: string }} [opts]
|
|
251
288
|
* @returns {{ stdout: string, exitCode: number, timedOut: boolean }}
|
|
252
289
|
*/
|
|
253
290
|
function runHook(hookFile, payload, opts = {}) {
|
|
254
|
-
|
|
291
|
+
if (!SHARED_HOOKS_DIR) return { stdout: '', exitCode: 0, timedOut: false };
|
|
292
|
+
const hookPath = path.join(SHARED_HOOKS_DIR, hookFile);
|
|
255
293
|
if (!fs.existsSync(hookPath)) return { stdout: '', exitCode: 0, timedOut: false };
|
|
256
294
|
const timeout = opts.timeout || 8000;
|
|
257
295
|
let result;
|
|
@@ -287,11 +325,16 @@ module.exports = function gsdPiExtension(pi) {
|
|
|
287
325
|
try {
|
|
288
326
|
({ dispatchGsdCommand } = require(path.join(GSD_CORE, 'bin', 'lib', 'shell-command-projection.cjs')));
|
|
289
327
|
} catch (e) {
|
|
290
|
-
return
|
|
328
|
+
// #2991: return the structured { content } shape Pi's ExtensionAPI
|
|
329
|
+
// displays, not a bare string (which Pi silently drops).
|
|
330
|
+
return { content: [{ type: 'text', text: `GSD engine unavailable: ${e && e.message ? e.message : String(e)}` }] };
|
|
291
331
|
}
|
|
292
332
|
const result = dispatchGsdCommand({ family, subcommand, args: rest, cwd });
|
|
293
|
-
|
|
294
|
-
|
|
333
|
+
// #2991: match gsd_invoke's proven output shape so Pi actually displays it.
|
|
334
|
+
const text = result.ok
|
|
335
|
+
? result.stdout
|
|
336
|
+
: `GSD error: ${result.stderr || result.stdout || `dispatch failed (exit ${result.code})`}`;
|
|
337
|
+
return { content: [{ type: 'text', text }] };
|
|
295
338
|
},
|
|
296
339
|
});
|
|
297
340
|
|
|
@@ -376,6 +419,8 @@ module.exports = function gsdPiExtension(pi) {
|
|
|
376
419
|
// resolution WITHOUT a live pi runtime.
|
|
377
420
|
module.exports._internals = {
|
|
378
421
|
resolveEngineRoot,
|
|
422
|
+
resolveSharedHooksDir,
|
|
423
|
+
SHARED_HOOKS_DIR_CANDIDATES,
|
|
379
424
|
parseGsdCommandArgs,
|
|
380
425
|
getArgumentCompletions,
|
|
381
426
|
PI_COMMAND_FAMILIES,
|
package/scripts/build-hooks.js
CHANGED
|
@@ -51,6 +51,13 @@ const HOOKS_TO_COPY = [
|
|
|
51
51
|
// .planning/config.json changes mid-session. Must ship to dist so the
|
|
52
52
|
// installer can copy it to the target hooks/ dir and register FileChanged.
|
|
53
53
|
'gsd-config-reload.js',
|
|
54
|
+
// Agent-dispatch isolation guard (#3045): blocks an executor Agent()
|
|
55
|
+
// dispatch missing its harness isolation parameter when the project
|
|
56
|
+
// resolves to harness-worktree. Requires the sibling
|
|
57
|
+
// gsd-core/bin/lib/{runtime-name-policy,capability-registry}.cjs modules
|
|
58
|
+
// at runtime — those ship as part of the full gsd-core/ tree, not via this
|
|
59
|
+
// list.
|
|
60
|
+
'gsd-agent-isolation-guard.js',
|
|
54
61
|
'gsd-prompt-guard.js',
|
|
55
62
|
'gsd-read-guard.js',
|
|
56
63
|
'gsd-read-injection-scanner.js',
|
|
@@ -58,6 +65,8 @@ const HOOKS_TO_COPY = [
|
|
|
58
65
|
'gsd-update-banner.js',
|
|
59
66
|
'gsd-workflow-guard.js',
|
|
60
67
|
'gsd-worktree-path-guard.js',
|
|
68
|
+
// Catastrophic-shrink guard for curated .planning/ artifacts (#2255, fix 3 of #973)
|
|
69
|
+
'gsd-write-guard.js',
|
|
61
70
|
// Community hooks (bash, opt-in via .planning/config.json hooks.community)
|
|
62
71
|
'gsd-session-state.sh',
|
|
63
72
|
'gsd-validate-commit.sh',
|
|
@@ -12,6 +12,10 @@
|
|
|
12
12
|
* Tests assert on the typed verdict, never on free text.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
+
// #2988: the repo's integration/default branch — the base every PR targets.
|
|
16
|
+
// Used as the local fallback when GITHUB_BASE_REF is unset (CI sets it).
|
|
17
|
+
const DEFAULT_BASE = 'next';
|
|
18
|
+
|
|
15
19
|
const LINT_REASON = Object.freeze({
|
|
16
20
|
OK_FRAGMENT_PRESENT: 'ok_fragment_present',
|
|
17
21
|
OK_OPT_OUT_LABEL: 'ok_opt_out_label',
|
|
@@ -80,7 +84,10 @@ function main() {
|
|
|
80
84
|
labels = (event.pull_request?.labels || []).map((l) => l.name);
|
|
81
85
|
} catch { /* fall through */ }
|
|
82
86
|
}
|
|
83
|
-
|
|
87
|
+
// #2988: local fallback must match the repo's integration branch (`next`),
|
|
88
|
+
// not the release branch (`main`). CI sets GITHUB_BASE_REF explicitly; the
|
|
89
|
+
// fallback only fires locally, where `next` is the base every PR targets.
|
|
90
|
+
const base = process.env.GITHUB_BASE_REF || DEFAULT_BASE;
|
|
84
91
|
let changedFiles = [];
|
|
85
92
|
try {
|
|
86
93
|
// Use execFileSync with an argv array — the base ref is interpolated
|
|
@@ -145,4 +152,4 @@ function main() {
|
|
|
145
152
|
|
|
146
153
|
if (require.main === module) runMain(main);
|
|
147
154
|
|
|
148
|
-
module.exports = { evaluateLint, LINT_REASON, OPT_OUT_LABEL, isUserFacing, isFragment };
|
|
155
|
+
module.exports = { evaluateLint, LINT_REASON, OPT_OUT_LABEL, isUserFacing, isFragment, DEFAULT_BASE };
|
|
@@ -26,7 +26,11 @@ function serializeChangelog(ir) {
|
|
|
26
26
|
lines.push(`### ${section.type}`);
|
|
27
27
|
lines.push('');
|
|
28
28
|
for (const b of section.bullets) {
|
|
29
|
-
lines
|
|
29
|
+
// #3001: indent continuation lines so parseChangelog's continuation-fold
|
|
30
|
+
// (/^\s+/) picks them up instead of terminating the bullet at the first
|
|
31
|
+
// blank line. A multi-paragraph body round-trips with content preserved.
|
|
32
|
+
const body = b.body.replace(/\n/g, '\n ');
|
|
33
|
+
lines.push(`- ${body} (#${b.pr})`);
|
|
30
34
|
}
|
|
31
35
|
lines.push('');
|
|
32
36
|
}
|
|
@@ -190,7 +190,7 @@ ${runtimeTable}
|
|
|
190
190
|
### Reviewer capabilities (role: reviewer) — ${reviewerCount}
|
|
191
191
|
|
|
192
192
|
Reviewer capabilities declare a cross-AI **reviewer lane** — one external CLI or
|
|
193
|
-
model endpoint \`/gsd
|
|
193
|
+
model endpoint \`/gsd-review\` hands a plan to (ADR-2782 D3). They are not install
|
|
194
194
|
targets: they emit no skills, agents, hooks or surface files, so their
|
|
195
195
|
extension-point and hook-kind cells are \`—\`. A host that is *also* a reviewer
|
|
196
196
|
(Claude, Codex, Cursor, OpenCode, Qwen, Antigravity) keeps one manifest and
|