session-orchestrator 3.22.0 → 3.23.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 +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor/commands/autopilot-multi.md +14 -0
- package/.cursor/commands/autopilot.md +14 -0
- package/.cursor/commands/bootstrap.md +14 -0
- package/.cursor/commands/brainstorm.md +14 -0
- package/.cursor/commands/close.md +13 -0
- package/.cursor/commands/contract-version-bump.md +14 -0
- package/.cursor/commands/debug.md +14 -0
- package/.cursor/commands/discovery.md +14 -0
- package/.cursor/commands/dispatcher.md +14 -0
- package/.cursor/commands/eli5.md +14 -0
- package/.cursor/commands/eval.md +14 -0
- package/.cursor/commands/evolve.md +14 -0
- package/.cursor/commands/go.md +14 -0
- package/.cursor/commands/grill.md +14 -0
- package/.cursor/commands/harness-audit.md +13 -0
- package/.cursor/commands/journey-audit.md +14 -0
- package/.cursor/commands/memory-cleanup.md +14 -0
- package/.cursor/commands/persona-panel.md +14 -0
- package/.cursor/commands/plan.md +14 -0
- package/.cursor/commands/portfolio.md +14 -0
- package/.cursor/commands/reconcile.md +14 -0
- package/.cursor/commands/release.md +14 -0
- package/.cursor/commands/repo-audit.md +13 -0
- package/.cursor/commands/session.md +14 -0
- package/.cursor/commands/spinout.md +14 -0
- package/.cursor/commands/sunset-review.md +14 -0
- package/.cursor/commands/templates-ack.md +14 -0
- package/.cursor/commands/test.md +14 -0
- package/.cursor/hooks.json +60 -0
- package/.cursor/rules/000-session-orchestrator.mdc +8 -0
- package/.cursor/rules/010-session-workflow.mdc +9 -1
- package/.cursor/rules/020-quality-gates.mdc +1 -1
- package/.cursor/rules/030-wave-execution.mdc +1 -1
- package/.cursor/rules/050-plan.mdc +2 -2
- package/.cursor/rules/070-gitlab-ops.mdc +73 -57
- package/.cursor/rules/080-ecosystem-health.mdc +7 -7
- package/.cursor/skills/architecture/SKILL.md +13 -0
- package/.cursor/skills/autopilot/SKILL.md +12 -0
- package/.cursor/skills/bootstrap/SKILL.md +12 -0
- package/.cursor/skills/brainstorm/SKILL.md +13 -0
- package/.cursor/skills/claude-md-drift-check/SKILL.md +13 -0
- package/.cursor/skills/contract-version-bump/SKILL.md +12 -0
- package/.cursor/skills/convergence-monitoring/SKILL.md +12 -0
- package/.cursor/skills/daily/SKILL.md +12 -0
- package/.cursor/skills/debug/SKILL.md +13 -0
- package/.cursor/skills/discovery/SKILL.md +13 -0
- package/.cursor/skills/dispatcher/SKILL.md +13 -0
- package/.cursor/skills/docs-orchestrator/SKILL.md +13 -0
- package/.cursor/skills/domain-model/SKILL.md +13 -0
- package/.cursor/skills/ecosystem-health/SKILL.md +13 -0
- package/.cursor/skills/eli5/SKILL.md +13 -0
- package/.cursor/skills/eval/SKILL.md +12 -0
- package/.cursor/skills/evolve/SKILL.md +13 -0
- package/.cursor/skills/frontmatter-guard/SKILL.md +13 -0
- package/.cursor/skills/gitlab-ops/SKILL.md +13 -0
- package/.cursor/skills/gitlab-portfolio/SKILL.md +13 -0
- package/.cursor/skills/grill/SKILL.md +13 -0
- package/.cursor/skills/hook-development/SKILL.md +13 -0
- package/.cursor/skills/journey-audit/SKILL.md +13 -0
- package/.cursor/skills/mcp-builder/SKILL.md +13 -0
- package/.cursor/skills/memory-cleanup/SKILL.md +12 -0
- package/.cursor/skills/mode-selector/SKILL.md +13 -0
- package/.cursor/skills/npm-publish/SKILL.md +12 -0
- package/.cursor/skills/peekaboo-driver/SKILL.md +13 -0
- package/.cursor/skills/persona-panel/SKILL.md +12 -0
- package/.cursor/skills/plan/SKILL.md +13 -0
- package/.cursor/skills/playwright-driver/SKILL.md +13 -0
- package/.cursor/skills/quality-gates/SKILL.md +13 -0
- package/.cursor/skills/reconcile/SKILL.md +12 -0
- package/.cursor/skills/repo-audit/SKILL.md +13 -0
- package/.cursor/skills/session-end/SKILL.md +13 -0
- package/.cursor/skills/session-plan/SKILL.md +13 -0
- package/.cursor/skills/session-start/SKILL.md +13 -0
- package/.cursor/skills/skill-creator/SKILL.md +13 -0
- package/.cursor/skills/spinout/SKILL.md +12 -0
- package/.cursor/skills/sunset-review/SKILL.md +13 -0
- package/.cursor/skills/test-runner/SKILL.md +13 -0
- package/.cursor/skills/tmux-layout/SKILL.md +13 -0
- package/.cursor/skills/ubiquitous-language/SKILL.md +13 -0
- package/.cursor/skills/using-orchestrator/SKILL.md +13 -0
- package/.cursor/skills/vault-mirror/SKILL.md +13 -0
- package/.cursor/skills/vault-sync/SKILL.md +13 -0
- package/.cursor/skills/wave-executor/SKILL.md +13 -0
- package/.cursor/skills/write-executable-plan/SKILL.md +13 -0
- package/.mcp.json +4 -1
- package/CHANGELOG.md +168 -0
- package/README.md +18 -15
- package/agents/AGENTS.md +23 -4
- package/agents/code-implementer.md +2 -1
- package/agents/db-specialist.md +2 -1
- package/agents/docs-writer.md +3 -1
- package/agents/eval-judge.md +1 -1
- package/agents/session-reviewer.md +7 -1
- package/agents/test-writer.md +2 -1
- package/agents/ui-developer.md +2 -1
- package/commands/bootstrap.md +2 -2
- package/commands/close.md +3 -1
- package/commands/go.md +1 -1
- package/commands/journey-audit.md +43 -0
- package/docs/USER-GUIDE.md +2 -2
- package/docs/ci-setup.md +14 -0
- package/docs/codex-setup.md +64 -0
- package/docs/components.md +6 -6
- package/docs/cursor-setup.md +26 -47
- package/docs/events-schema.md +76 -4
- package/docs/github-mirror-protection.md +197 -0
- package/docs/pi-setup.md +2 -0
- package/docs/rule-authoring.md +3 -1
- package/docs/scope-collision-guard.md +49 -2
- package/docs/session-config-reference.md +26 -4
- package/docs/session-config-template.md +4 -3
- package/docs/telemetry.md +22 -0
- package/hooks/_lib/lock-bootstrap.mjs +8 -4
- package/hooks/_lib/vcs-create-matcher.mjs +397 -38
- package/hooks/enforce-scope.mjs +64 -0
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks-cursor.json +201 -20
- package/hooks/hooks-pi.json +1 -1
- package/hooks/hooks.json +2 -2
- package/hooks/on-session-end.mjs +211 -10
- package/hooks/on-session-start.mjs +214 -11
- package/hooks/on-stop.mjs +48 -9
- package/hooks/post-subagent-discovery-validator.mjs +34 -3
- package/hooks/post-tool-batch-wave-signal.mjs +11 -2
- package/hooks/pre-bash-issue-budget.mjs +117 -4
- package/hooks/pre-bash-sessions-ledger-guard.mjs +159 -0
- package/hooks/pre-bash-staging-fence.mjs +4 -0
- package/hooks/pre-task-scope-disjoint.mjs +368 -35
- package/hooks/skill-invocation-telemetry.mjs +21 -10
- package/monitors/monitors.json +6 -0
- package/package.json +1 -1
- package/pi/prompts/journey-audit.md +12 -0
- package/rules/_index.md +9 -1
- package/rules/always-on/ask-via-tool.md +62 -0
- package/rules/always-on/bash-harness-pitfalls.md +168 -0
- package/rules/always-on/build-value.md +47 -0
- package/rules/always-on/cross-session-messaging.md +59 -0
- package/rules/always-on/loop-and-monitor.md +221 -0
- package/rules/always-on/parallel-sessions.md +142 -12
- package/rules/always-on/receiving-review.md +108 -0
- package/rules/always-on/test-value.md +40 -0
- package/rules/always-on/verification-before-completion.md +77 -0
- package/scripts/archive-closed-prds.mjs +258 -18
- package/scripts/autopilot.mjs +5 -0
- package/scripts/backfill-evidence-digest.mjs +376 -0
- package/scripts/cursor-install.mjs +89 -48
- package/scripts/export-hw-learnings.mjs +143 -2
- package/scripts/express-path.mjs +299 -0
- package/scripts/generate-cursor-adapter.mjs +253 -0
- package/scripts/github-protection-audit.mjs +358 -0
- package/scripts/lib/autopilot/worktree-pipeline.mjs +240 -16
- package/scripts/lib/build-live-signals.mjs +24 -5
- package/scripts/lib/ci-status-banner.mjs +158 -11
- package/scripts/lib/command-blocker.mjs +70 -0
- package/scripts/lib/config/reconcile.mjs +79 -4
- package/scripts/lib/config/section-extractor.mjs +235 -36
- package/scripts/lib/config-schema.mjs +9 -1
- package/scripts/lib/config.mjs +57 -6
- package/scripts/lib/convergence-monitor.mjs +13 -2
- package/scripts/lib/cursor-hook-bridge.mjs +443 -0
- package/scripts/lib/dispatcher/cli.mjs +2 -2
- package/scripts/lib/express-path.mjs +327 -0
- package/scripts/lib/file-lock.mjs +22 -4
- package/scripts/lib/gates/gate-full.mjs +81 -8
- package/scripts/lib/gates/gate-helpers.mjs +76 -15
- package/scripts/lib/git-config-drift.mjs +134 -5
- package/scripts/lib/host-identity.mjs +247 -2
- package/scripts/lib/instruction-budget-guard.mjs +31 -1
- package/scripts/lib/issue-budget.mjs +229 -30
- package/scripts/lib/learnings/io.mjs +55 -10
- package/scripts/lib/learnings/schema.mjs +95 -28
- package/scripts/lib/lock-reaper.mjs +7 -1
- package/scripts/lib/locks/staging-fence-lock.mjs +5 -1
- package/scripts/lib/locks/state-md-lock.mjs +8 -1
- package/scripts/lib/memory-banner.mjs +5 -2
- package/scripts/lib/memory-paths.mjs +15 -6
- package/scripts/lib/mode-selector/scoring.mjs +53 -6
- package/scripts/lib/platform.mjs +72 -9
- package/scripts/lib/plugin-root.mjs +143 -19
- package/scripts/lib/project-hygiene.mjs +43 -3
- package/scripts/lib/quality-gate.mjs +271 -13
- package/scripts/lib/reconcile/emitter.mjs +87 -19
- package/scripts/lib/reconcile/engine.mjs +281 -13
- package/scripts/lib/reconcile/idempotency.mjs +102 -1
- package/scripts/lib/reconcile/renderer.mjs +148 -3
- package/scripts/lib/reconcile/sanitize.mjs +40 -17
- package/scripts/lib/reconcile/writer.mjs +415 -84
- package/scripts/lib/rule-loader.mjs +37 -2
- package/scripts/lib/rules-sync.mjs +51 -8
- package/scripts/lib/scope-gate.mjs +90 -0
- package/scripts/lib/session-close-backfill.mjs +369 -28
- package/scripts/lib/session-discovery.mjs +13 -3
- package/scripts/lib/session-end/phase-skip.mjs +37 -4
- package/scripts/lib/session-end/worktree-cleanup.mjs +154 -7
- package/scripts/lib/session-id.mjs +30 -14
- package/scripts/lib/session-identity/own-session.mjs +159 -0
- package/scripts/lib/session-lock.mjs +85 -30
- package/scripts/lib/session-schema/normalizer.mjs +70 -3
- package/scripts/lib/session-schema/validator.mjs +40 -0
- package/scripts/lib/session-start-probes.mjs +608 -0
- package/scripts/lib/session-transition.mjs +277 -0
- package/scripts/lib/sessions-staleness-banner.mjs +124 -57
- package/scripts/lib/spiral-carryover.mjs +90 -9
- package/scripts/lib/state-md/frontmatter-mutators.mjs +41 -8
- package/scripts/lib/state-md/mission-status.mjs +350 -52
- package/scripts/lib/state-md/yaml-parser.mjs +145 -16
- package/scripts/lib/state-md.mjs +12 -2
- package/scripts/lib/telemetry/sync.mjs +46 -8
- package/scripts/lib/validate/check-agents.mjs +66 -0
- package/scripts/lib/validate/check-cursor-adapter.mjs +102 -0
- package/scripts/lib/validate/check-dead-bridge.mjs +24 -2
- package/scripts/lib/validate/check-doc-cli-commands.mjs +16 -32
- package/scripts/lib/validate/check-hooks-symmetry.mjs +29 -63
- package/scripts/lib/validate/check-playwright-mcp-canary.mjs +13 -22
- package/scripts/lib/validate/check-plugin-monitors.mjs +10 -4
- package/scripts/lib/validate/check-test-value-bans.mjs +165 -17
- package/scripts/lib/validate/check-unwired-features.mjs +340 -32
- package/scripts/lib/validate/repo-files.mjs +275 -0
- package/scripts/lib/validate-vendored-rules.mjs +229 -7
- package/scripts/lib/vault-mirror/process.mjs +99 -43
- package/scripts/lib/vault-mirror/telemetry.mjs +210 -0
- package/scripts/lib/vault-staleness-banner.mjs +76 -6
- package/scripts/lib/vault-status/board-writer.mjs +211 -10
- package/scripts/lib/vault-status/narrative-mirror.mjs +188 -8
- package/scripts/lib/wave-executor/foreign-dispatch.mjs +832 -0
- package/scripts/lib/wave-transcript-tail.mjs +869 -0
- package/scripts/materialize-wave-scope.mjs +209 -12
- package/scripts/mcp-server.sh +11 -2
- package/scripts/parse-config.mjs +65 -0
- package/scripts/token-audit.sh +9 -2
- package/scripts/validate-plugin.mjs +3 -0
- package/scripts/validate-wave-scope.mjs +67 -0
- package/scripts/vault-mirror.mjs +203 -34
- package/skills/_shared/monitor-patterns.md +31 -5
- package/skills/_shared/parallel-aware-auq.md +1 -1
- package/skills/_shared/parallel-aware-preamble.md +4 -2
- package/skills/_shared/platform-tools.md +11 -5
- package/skills/_shared/state-ownership.md +29 -2
- package/skills/autopilot/SKILL.md +5 -1
- package/skills/bootstrap/SKILL.md +3 -3
- package/skills/bootstrap/_shared-template.md +18 -10
- package/skills/bootstrap/deep-template.md +10 -6
- package/skills/bootstrap/fast-template.md +15 -8
- package/skills/bootstrap/standard-template.md +10 -6
- package/skills/claude-md-drift-check/checker.mjs +39 -11
- package/skills/dispatcher/SKILL.md +1 -1
- package/skills/journey-audit/SKILL.md +269 -0
- package/skills/peekaboo-driver/SKILL.md +15 -3
- package/skills/persona-panel/SKILL.md +1 -1
- package/skills/reconcile/SKILL.md +41 -1
- package/skills/session-end/SKILL.md +17 -4
- package/skills/session-end/metrics-collection.md +7 -4
- package/skills/session-end/phase-3-6-tail.md +11 -3
- package/skills/session-end/phase-3-7a-recommendations.md +16 -2
- package/skills/session-plan/SKILL.md +6 -1
- package/skills/session-plan/wave-template.md +1 -0
- package/skills/session-start/SKILL.md +30 -16
- package/skills/session-start/phase-7-5-mode-selector.md +15 -3
- package/skills/session-start/phase-8-5-express-path.md +77 -12
- package/skills/vault-sync/validator.mjs +31 -0
- package/skills/wave-executor/SKILL.md +4 -2
- package/skills/wave-executor/circuit-breaker.md +34 -9
- package/skills/wave-executor/wave-loop.md +102 -19
- package/templates/_shared/journey-manifest.md +110 -0
- package/templates/_shared/rules/parallel-sessions.md +0 -77
|
@@ -43,6 +43,7 @@ import {
|
|
|
43
43
|
formatBlockReason,
|
|
44
44
|
resolveIssueBudgetSessionId,
|
|
45
45
|
} from './issue-budget.mjs';
|
|
46
|
+
import { resolveProjectDir } from './platform.mjs';
|
|
46
47
|
import { resolveRepoSpec } from './vcs-repo-spec.mjs';
|
|
47
48
|
|
|
48
49
|
/**
|
|
@@ -83,6 +84,27 @@ function isIssueCreateArgv(cmd, args) {
|
|
|
83
84
|
return args[0] === 'issue' && (args[1] === 'create' || args[1] === 'new');
|
|
84
85
|
}
|
|
85
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Under vitest, a `repoRoot` that resolves to the ambient working copy is almost
|
|
89
|
+
* always the default-parameter leak (#1105): the test meant to pass a synthetic
|
|
90
|
+
* `mkdtemp` root but did not, and the issue-budget ledger would be charged in
|
|
91
|
+
* the operator's real tree. Production keeps the cwd default unchanged; this
|
|
92
|
+
* gate only fires when `process.env.VITEST` is set.
|
|
93
|
+
*
|
|
94
|
+
* @param {string} budgetRoot — ledger root about to be passed to `chargeIssueBudget`
|
|
95
|
+
* @returns {string|null} refusal message, or null when charging may proceed
|
|
96
|
+
*/
|
|
97
|
+
function refuseAmbientLedgerUnderVitest(budgetRoot) {
|
|
98
|
+
if (!process.env.VITEST) return null;
|
|
99
|
+
if (path.resolve(budgetRoot) === path.resolve(process.cwd())) {
|
|
100
|
+
return (
|
|
101
|
+
'spiral-carryover: refusing to charge the issue-budget ledger at the ambient ' +
|
|
102
|
+
'repo root under VITEST — pass an explicit synthetic repoRoot'
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
86
108
|
/**
|
|
87
109
|
* Run a CLI command and return { ok, stdout, stderr }. Never throws.
|
|
88
110
|
*
|
|
@@ -97,14 +119,46 @@ function isIssueCreateArgv(cmd, args) {
|
|
|
97
119
|
* exactly the session-end promise at SKILL.md:319 / :1113 being preserved. It
|
|
98
120
|
* bites for any FUTURE non-exempt producer added to this module.
|
|
99
121
|
*
|
|
122
|
+
* REPO BINDING (#1058 follow-on). The ledger root is the `repoRoot` the CALLER
|
|
123
|
+
* named — the same value that already decides the `-R` host-pinning spec a few
|
|
124
|
+
* lines down. Before this parameter existed, `runCli` re-derived it from
|
|
125
|
+
* `process.env.CLAUDE_PROJECT_DIR || process.cwd()`, so one call could file an
|
|
126
|
+
* issue into repo A (via `-R`) while charging repo B's budget ledger. Two
|
|
127
|
+
* answers to "which repo" inside one call path is the defect; the caller's
|
|
128
|
+
* answer is the authoritative one.
|
|
129
|
+
*
|
|
130
|
+
* That split brain was also a live test leak, measured 2026-08-23: a sandboxed
|
|
131
|
+
* `CLAUDE_PROJECT_DIR=$(mktemp -d) npx vitest run tests/lib/spiral-carryover.test.mjs`
|
|
132
|
+
* left `{"sessionId":"1c2e5507-…","count":0,"exempt":15}` in the sandbox — 15
|
|
133
|
+
* bookings per run, carrying the REAL session id, which under a plain
|
|
134
|
+
* `npm test` land in this repo's live issue-budget ledger (since #1141 the
|
|
135
|
+
* per-session file `.orchestrator/runtime/issue-budget/<hash>.json`; the path
|
|
136
|
+
* is owned by `budgetStateRel` in `scripts/lib/issue-budget.mjs` — no caller,
|
|
137
|
+
* including this one, spells it out).
|
|
138
|
+
*
|
|
139
|
+
* The `repoRoot`-less fallback is `resolveProjectDir()` from `platform.mjs` —
|
|
140
|
+
* the SAME resolver `hooks/pre-bash-issue-budget.mjs` uses, so both producers
|
|
141
|
+
* of this ledger agree on which file they are charging. It strictly supersedes
|
|
142
|
+
* the hand-rolled expression it replaces: env fast-path first (including the
|
|
143
|
+
* Codex/Cursor/pi variants), then a walk-up for the instruction file — `CLAUDE.md`
|
|
144
|
+
* on Claude Code, `AGENTS.md` on Codex CLI (transparent aliases) — or `.git`, then cwd.
|
|
145
|
+
*
|
|
100
146
|
* @param {string} cmd
|
|
101
147
|
* @param {string[]} args
|
|
148
|
+
* @param {string} [repoRoot] — repo whose issue-budget ledger this call is
|
|
149
|
+
* charged against. Falls back to `resolveProjectDir()` when absent.
|
|
102
150
|
* @returns {{ ok: boolean, stdout: string, stderr: string, budgetBlocked?: boolean }}
|
|
103
151
|
*/
|
|
104
|
-
function runCli(cmd, args) {
|
|
152
|
+
function runCli(cmd, args, repoRoot) {
|
|
105
153
|
if (isIssueCreateArgv(cmd, args)) {
|
|
106
154
|
try {
|
|
107
|
-
const
|
|
155
|
+
const budgetRoot = (typeof repoRoot === 'string' && repoRoot.trim())
|
|
156
|
+
? repoRoot
|
|
157
|
+
: (resolveProjectDir() || process.cwd());
|
|
158
|
+
const vitestLedgerRefusal = refuseAmbientLedgerUnderVitest(budgetRoot);
|
|
159
|
+
if (vitestLedgerRefusal) {
|
|
160
|
+
return { ok: false, stdout: '', stderr: vitestLedgerRefusal, budgetBlocked: true };
|
|
161
|
+
}
|
|
108
162
|
// The harness exports CLAUDE_CODE_SESSION_ID (measured 2026-08-21: it is
|
|
109
163
|
// present in the Bash tool environment this module runs in). There is no
|
|
110
164
|
// CLAUDE_SESSION_ID — reading that name made this whole block dead code
|
|
@@ -113,16 +167,43 @@ function runCli(cmd, args) {
|
|
|
113
167
|
let currentSession = null;
|
|
114
168
|
if (typeof nativeRawId === 'string' && nativeRawId.length > 0) {
|
|
115
169
|
try {
|
|
116
|
-
|
|
117
|
-
readFileSync(path.join(
|
|
170
|
+
const parsed = JSON.parse(
|
|
171
|
+
readFileSync(path.join(budgetRoot, '.orchestrator', 'current-session.json'), 'utf8'),
|
|
118
172
|
);
|
|
173
|
+
// SESSION BINDING (#1058). `current-session.json` is repo-global —
|
|
174
|
+
// every session sharing this working copy writes the same path — so
|
|
175
|
+
// the file in hand may describe a PEER session, not us.
|
|
176
|
+
//
|
|
177
|
+
// The binding itself is not new: `resolveIssueBudgetSessionId`
|
|
178
|
+
// already requires `currentSession.session_id === nativeRawId` before
|
|
179
|
+
// it will adopt the file's semantic label, and falls back to the raw
|
|
180
|
+
// env id otherwise. What was missing is that the mismatch was
|
|
181
|
+
// INVISIBLE — the comparison lives one module over, so a foreign file
|
|
182
|
+
// was discarded with no trace, which is the same error class as using
|
|
183
|
+
// it silently. Deciding it HERE makes the outcome identical and the
|
|
184
|
+
// reason audible; it deliberately does not restate the wider
|
|
185
|
+
// multi-id-space classifier `scripts/lib/quality-gate.mjs` needs
|
|
186
|
+
// (that reader has no raw env id guaranteed in hand — this one does,
|
|
187
|
+
// by the `typeof nativeRawId === 'string'` guard above).
|
|
188
|
+
const foreignId = typeof parsed?.session_id === 'string' && parsed.session_id.length > 0
|
|
189
|
+
? parsed.session_id
|
|
190
|
+
: null;
|
|
191
|
+
if (foreignId !== null && foreignId !== nativeRawId) {
|
|
192
|
+
process.stderr.write(
|
|
193
|
+
'⚠️ spiral-carryover: .orchestrator/current-session.json belongs to another ' +
|
|
194
|
+
`session (${foreignId}) — using the native session id for issue-budget ` +
|
|
195
|
+
'accounting. Another session is active in this working copy (PSA-001).\n',
|
|
196
|
+
);
|
|
197
|
+
} else {
|
|
198
|
+
currentSession = parsed;
|
|
199
|
+
}
|
|
119
200
|
} catch {
|
|
120
201
|
// Missing or malformed records conservatively retain the native env key.
|
|
121
202
|
}
|
|
122
203
|
}
|
|
123
204
|
const titleIdx = args.indexOf('--title');
|
|
124
205
|
const verdict = chargeIssueBudget({
|
|
125
|
-
repoRoot,
|
|
206
|
+
repoRoot: budgetRoot,
|
|
126
207
|
sessionId: resolveIssueBudgetSessionId(nativeRawId, currentSession),
|
|
127
208
|
command: [cmd, ...args].join(' '),
|
|
128
209
|
title: titleIdx >= 0 ? (args[titleIdx + 1] ?? null) : null,
|
|
@@ -211,7 +292,7 @@ async function findExistingLabeledIssue({
|
|
|
211
292
|
'number,url,body',
|
|
212
293
|
];
|
|
213
294
|
if (spec) args.push('-R', spec);
|
|
214
|
-
const res = runCli('gh', args);
|
|
295
|
+
const res = runCli('gh', args, repoRoot);
|
|
215
296
|
if (!res.ok) return { exists: false };
|
|
216
297
|
let arr;
|
|
217
298
|
try {
|
|
@@ -231,7 +312,7 @@ async function findExistingLabeledIssue({
|
|
|
231
312
|
// Default: gitlab via glab.
|
|
232
313
|
const args = ['issue', 'list', '--label', label, '--per-page', '100', '--output', 'json'];
|
|
233
314
|
if (spec) args.push('-R', spec);
|
|
234
|
-
const res = runCli('glab', args);
|
|
315
|
+
const res = runCli('glab', args, repoRoot);
|
|
235
316
|
if (!res.ok) return { exists: false };
|
|
236
317
|
let arr;
|
|
237
318
|
try {
|
|
@@ -420,7 +501,7 @@ export async function createSpiralCarryoverIssue({
|
|
|
420
501
|
}
|
|
421
502
|
if (spec) args.push('-R', spec);
|
|
422
503
|
|
|
423
|
-
const res = runCli(cmd, args);
|
|
504
|
+
const res = runCli(cmd, args, repoRoot);
|
|
424
505
|
if (!res.ok) {
|
|
425
506
|
return { created: false, skipped: 'error', error: res.stderr.trim() || 'CLI invocation failed' };
|
|
426
507
|
}
|
|
@@ -609,7 +690,7 @@ export async function createBrokenWindowIssue({
|
|
|
609
690
|
}
|
|
610
691
|
if (spec) args.push('-R', spec);
|
|
611
692
|
|
|
612
|
-
const res = runCli(cmd, args);
|
|
693
|
+
const res = runCli(cmd, args, repoRoot);
|
|
613
694
|
if (!res.ok) {
|
|
614
695
|
return { created: false, skipped: 'error', error: res.stderr.trim() || 'CLI invocation failed' };
|
|
615
696
|
}
|
|
@@ -239,11 +239,20 @@ function extractFrontmatterBlock(text) {
|
|
|
239
239
|
*
|
|
240
240
|
* Scoped to the frontmatter block only (see the "Frontmatter-safe round-trip
|
|
241
241
|
* guard" comment above `DEFAULT_STATE_MD_SIZE_CEILING_BYTES` for the scoping
|
|
242
|
-
* rationale).
|
|
243
|
-
*
|
|
244
|
-
*
|
|
245
|
-
*
|
|
246
|
-
*
|
|
242
|
+
* rationale).
|
|
243
|
+
*
|
|
244
|
+
* `parseStateMd(after) === null` has TWO causes and they get OPPOSITE verdicts,
|
|
245
|
+
* because collapsing them is what made this guard blind:
|
|
246
|
+
* - NO frontmatter fence in `after` → SAFE. There is nothing frontmatter-shaped
|
|
247
|
+
* to verify, and non-STATE.md-shaped writes (arbitrary test fixtures, plain
|
|
248
|
+
* body text) stay ungated, matching `evaluateSizeCeiling`'s content-agnostic
|
|
249
|
+
* posture for that case.
|
|
250
|
+
* - A fence IS present but its contents do not parse → UNSAFE. A write whose
|
|
251
|
+
* frontmatter this repo's own parser cannot read back is precisely the
|
|
252
|
+
* corruption class this guard exists to refuse; treating it as "nothing to
|
|
253
|
+
* verify" let the single worst input pass unchecked (the 2026-08-24 #1111
|
|
254
|
+
* regression, where one flow item nulled the whole document, sat in exactly
|
|
255
|
+
* this hole).
|
|
247
256
|
*
|
|
248
257
|
* Never throws — mirrors the never-throw contract of yaml-parser.mjs.
|
|
249
258
|
*
|
|
@@ -251,12 +260,20 @@ function extractFrontmatterBlock(text) {
|
|
|
251
260
|
* @returns {{ unsafe: boolean, reason: string|null }}
|
|
252
261
|
*/
|
|
253
262
|
export function evaluateFrontmatterSafe(after) {
|
|
263
|
+
const beforeBlock = extractFrontmatterBlock(after);
|
|
254
264
|
const parsed = parseStateMd(after);
|
|
255
265
|
if (parsed === null) {
|
|
256
|
-
|
|
266
|
+
if (beforeBlock === null) {
|
|
267
|
+
return { unsafe: false, reason: null };
|
|
268
|
+
}
|
|
269
|
+
return {
|
|
270
|
+
unsafe: true,
|
|
271
|
+
reason:
|
|
272
|
+
`frontmatter block is present but parseStateMd could not read it back ` +
|
|
273
|
+
`(${Buffer.byteLength(beforeBlock, 'utf8')}B unparseable)`,
|
|
274
|
+
};
|
|
257
275
|
}
|
|
258
276
|
const reserialized = serializeStateMd(parsed);
|
|
259
|
-
const beforeBlock = extractFrontmatterBlock(after);
|
|
260
277
|
const afterBlock = extractFrontmatterBlock(reserialized);
|
|
261
278
|
if (beforeBlock !== afterBlock) {
|
|
262
279
|
return {
|
|
@@ -320,7 +337,10 @@ function writeFileAtomic(filePath, contents) {
|
|
|
320
337
|
* deliberately left unshipped when issue #739 first landed, because the
|
|
321
338
|
* yaml-parser asymmetry it exists to catch made it false-positive on ordinary
|
|
322
339
|
* content at the time) — #747 closed that asymmetry in yaml-parser.mjs, so
|
|
323
|
-
* the check is safe to enforce.
|
|
340
|
+
* the check is safe to enforce. A frontmatter fence that is present but does
|
|
341
|
+
* NOT parse back is refused by the same guard (see `evaluateFrontmatterSafe`);
|
|
342
|
+
* only content with no fence at all leaves it inert. It catches FUTURE
|
|
343
|
+
* serializer/parser drift
|
|
324
344
|
* that reintroduces a non-idempotent frontmatter round-trip — the exact
|
|
325
345
|
* incident class behind #739 — rather than the symptom (file size) the
|
|
326
346
|
* size-ceiling guard was limited to. Same non-throw, WARN-then-refuse
|
|
@@ -395,6 +415,19 @@ export async function writeStateMd(repoRoot, transformer, opts = {}) {
|
|
|
395
415
|
return { written: false, path: statePath, contents: before, reason: 'frontmatter-unsafe' };
|
|
396
416
|
}
|
|
397
417
|
|
|
418
|
+
// A list item the parser cannot represent is dropped on READ (see
|
|
419
|
+
// yaml-parser.mjs `parseBlockValue`), so this write is what makes the drop
|
|
420
|
+
// permanent on disk. Dropping is the right call — the alternatives are a
|
|
421
|
+
// mangled key or a dead no-op — but it must not be SILENT, which is the
|
|
422
|
+
// whole complaint the per-item scoping answers. Name the items, then write
|
|
423
|
+
// anyway: blocking here would re-create the no-op this fix removed.
|
|
424
|
+
const droppedItems = parseStateMd(before)?.warnings;
|
|
425
|
+
if (droppedItems !== undefined) {
|
|
426
|
+
process.stderr.write(
|
|
427
|
+
`⚠ writeStateMd: ${statePath} — dropping ${droppedItems.length} unrepresentable list item(s): ` +
|
|
428
|
+
`${droppedItems.map((w) => `${w.key}[${w.index}] (${w.reason})`).join(', ')}\n`
|
|
429
|
+
);
|
|
430
|
+
}
|
|
398
431
|
writeFileAtomic(statePath, after);
|
|
399
432
|
return { written: true, path: statePath, contents: after };
|
|
400
433
|
},
|