mandrel 2.36.0 → 2.38.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/.agents/README.md +16 -16
- package/.agents/agents/plan-critic.md +1 -1
- package/.agents/docs/SDLC.md +40 -40
- package/.agents/docs/configuration.md +41 -16
- package/.agents/docs/execution-reference.md +3 -3
- package/.agents/docs/quality-gates.md +2 -2
- package/.agents/docs/workflows.md +6 -6
- package/.agents/instructions.md +5 -5
- package/.agents/rules/changelog-style.md +3 -3
- package/.agents/rules/ci-remediation.md +1 -1
- package/.agents/rules/git-conventions-reference.md +4 -4
- package/.agents/rules/testing-standards.md +56 -0
- package/.agents/schemas/agentrc.schema.json +9 -9
- package/.agents/schemas/story-deliver-terminal.schema.json +2 -2
- package/.agents/schemas/validation-evidence.schema.json +1 -1
- package/.agents/scripts/acceptance-eval.js +1 -1
- package/.agents/scripts/apply-quality-bootstrap.js +1 -1
- package/.agents/scripts/audit-to-stories.js +2 -2
- package/.agents/scripts/boot-sweep.js +7 -1
- package/.agents/scripts/check-context-budget.js +195 -103
- package/.agents/scripts/check-cyclomatic.js +112 -42
- package/.agents/scripts/check-doc-links.js +32 -4
- package/.agents/scripts/check-generated-validator.js +202 -0
- package/.agents/scripts/check-lifecycle-lint.js +2 -68
- package/.agents/scripts/check-schema-references.js +2 -28
- package/.agents/scripts/check-test-temp-hygiene.js +1 -1
- package/.agents/scripts/check-workflow-cli-lint.js +1 -1
- package/.agents/scripts/deliver-light.js +10 -10
- package/.agents/scripts/deliver-recover.js +1 -1
- package/.agents/scripts/drain-pending-cleanup.js +19 -2
- package/.agents/scripts/evidence-gate.js +1 -1
- package/.agents/scripts/generate-workflows-doc.js +1 -1
- package/.agents/scripts/lib/audit-suite/selector.js +1 -1
- package/.agents/scripts/lib/audit-to-stories/audit-label-taxonomy.js +1 -1
- package/.agents/scripts/lib/audit-to-stories/build-story-body.js +1 -1
- package/.agents/scripts/lib/audit-to-stories/seed-from-findings.js +5 -5
- package/.agents/scripts/lib/audit-to-stories/wire-dependencies.js +1 -1
- package/.agents/scripts/lib/baselines/components.js +32 -2
- package/.agents/scripts/lib/baselines/env-overrides.js +1 -1
- package/.agents/scripts/lib/bdd-runner-detect.js +1 -1
- package/.agents/scripts/lib/bdd-scenario-budget.js +1 -1
- package/.agents/scripts/lib/bdd-scenario-scanner.js +3 -3
- package/.agents/scripts/lib/bdd-step-index.js +1 -1
- package/.agents/scripts/lib/bootstrap/branch-protection.js +2 -2
- package/.agents/scripts/lib/bootstrap/commit-push.js +2 -2
- package/.agents/scripts/lib/bootstrap/install-ledger.js +1 -1
- package/.agents/scripts/lib/bootstrap/issue-forms-template.js +1 -1
- package/.agents/scripts/lib/bootstrap/prompt.js +1 -1
- package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +2 -2
- package/.agents/scripts/lib/checks/core-bare-clean.js +1 -1
- package/.agents/scripts/lib/checks/loop-health.js +1 -1
- package/.agents/scripts/lib/checks/story-init-not-backgrounded.js +1 -1
- package/.agents/scripts/lib/cli-args.js +1 -3
- package/.agents/scripts/lib/close-validation/gates.js +1 -1
- package/.agents/scripts/lib/close-validation/process.js +1 -1
- package/.agents/scripts/lib/command-header.js +1 -1
- package/.agents/scripts/lib/config/delivery-routing.js +1 -1
- package/.agents/scripts/lib/config/explain.js +1 -1
- package/.agents/scripts/lib/config/runners.js +2 -2
- package/.agents/scripts/lib/config/runtime.js +1 -1
- package/.agents/scripts/lib/config/sync-agentrc.js +1 -1
- package/.agents/scripts/lib/config/temp-paths.js +2 -2
- package/.agents/scripts/lib/config-settings-schema-delivery.js +3 -3
- package/.agents/scripts/lib/config-settings-schema-quality.js +6 -6
- package/.agents/scripts/lib/config-settings-schema.js +49 -9
- package/.agents/scripts/lib/coverage-baseline.js +2 -2
- package/.agents/scripts/lib/cpu-pool.js +90 -10
- package/.agents/scripts/lib/crap-utils.js +6 -2
- package/.agents/scripts/lib/cyclomatic-ceiling.js +28 -6
- package/.agents/scripts/lib/cyclomatic-scope.js +144 -0
- package/.agents/scripts/lib/dependency-version.js +86 -0
- package/.agents/scripts/lib/duplicate-search.js +2 -2
- package/.agents/scripts/lib/errors/index.js +1 -1
- package/.agents/scripts/lib/feedback-loop/graduator-core.js +2 -2
- package/.agents/scripts/lib/feedback-loop/prior-feedback-fetcher.js +5 -5
- package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +2 -2
- package/.agents/scripts/lib/findings/classify-finding.js +1 -1
- package/.agents/scripts/lib/findings/promote-finding.js +12 -12
- package/.agents/scripts/lib/findings/route-finding.js +2 -2
- package/.agents/scripts/lib/generated/agentrc-validator.js +16 -0
- package/.agents/scripts/lib/git-branch-lifecycle.js +2 -2
- package/.agents/scripts/lib/install-cmd-parser.js +1 -1
- package/.agents/scripts/lib/knip-entry-sync.js +1 -57
- package/.agents/scripts/lib/label-constants.js +2 -2
- package/.agents/scripts/lib/label-taxonomy.js +1 -1
- package/.agents/scripts/lib/maintainability-utils.js +108 -10
- package/.agents/scripts/lib/observability/source-classifier.js +1 -0
- package/.agents/scripts/lib/onboard/init-tail.js +4 -4
- package/.agents/scripts/lib/onboard/scaffold-docs.js +2 -2
- package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +1 -1
- package/.agents/scripts/lib/orchestration/code-review.js +4 -4
- package/.agents/scripts/lib/orchestration/complexity-gate.js +5 -5
- package/.agents/scripts/lib/orchestration/deliver-recover.js +3 -3
- package/.agents/scripts/lib/orchestration/docs-digest.js +3 -3
- package/.agents/scripts/lib/orchestration/lease-guard-shared.js +2 -2
- package/.agents/scripts/lib/orchestration/light-backstop.js +1 -1
- package/.agents/scripts/lib/orchestration/light-escalation.js +6 -6
- package/.agents/scripts/lib/orchestration/light-suitability.js +19 -19
- package/.agents/scripts/lib/orchestration/plan-context.js +4 -4
- package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +2 -2
- package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +2 -2
- package/.agents/scripts/lib/orchestration/plan-metrics.js +1 -1
- package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +1 -1
- package/.agents/scripts/lib/orchestration/plan-persist/plan-context-source.js +3 -3
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +6 -6
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +86 -25
- package/.agents/scripts/lib/orchestration/plan-persist/summary.js +3 -3
- package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +6 -6
- package/.agents/scripts/lib/orchestration/planning/authoring-context.js +7 -7
- package/.agents/scripts/lib/orchestration/planning/decomposer-context.js +1 -1
- package/.agents/scripts/lib/orchestration/planning/memory-pool-advisory.js +3 -3
- package/.agents/scripts/lib/orchestration/pr-watch.js +1 -30
- package/.agents/scripts/lib/orchestration/reassert-status-column.js +2 -2
- package/.agents/scripts/lib/orchestration/remote-verifier.js +2 -2
- package/.agents/scripts/lib/orchestration/resolve-stories.js +4 -4
- package/.agents/scripts/lib/orchestration/review-providers/codex.js +3 -3
- package/.agents/scripts/lib/orchestration/review-providers/findings-renderer.js +1 -1
- package/.agents/scripts/lib/orchestration/review-providers/native.js +9 -2
- package/.agents/scripts/lib/orchestration/review-providers/review-depth.js +2 -2
- package/.agents/scripts/lib/orchestration/review-providers/security-review.js +3 -3
- package/.agents/scripts/lib/orchestration/run-epilogue.js +2 -2
- package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +2 -2
- package/.agents/scripts/lib/orchestration/single-story-close/phases/conventional-subject.js +1 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/normalize-pr-title.js +5 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +52 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +1 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +3 -3
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +2 -2
- package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +1 -1
- package/.agents/scripts/lib/orchestration/story-close/format-autofix.js +2 -5
- package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +14 -12
- package/.agents/scripts/lib/orchestration/story-follow-ups.js +1 -1
- package/.agents/scripts/lib/orchestration/ticket-lease.js +34 -9
- package/.agents/scripts/lib/orchestration/ticket-validator.js +1 -1
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +6 -4
- package/.agents/scripts/lib/plan-phase-cleanup.js +1 -1
- package/.agents/scripts/lib/preflight-runner.js +3 -3
- package/.agents/scripts/lib/qa/qa-session.js +1 -1
- package/.agents/scripts/lib/runtime-deps/manifest.js +1 -7
- package/.agents/scripts/lib/runtime-deps/scan-imports.js +3 -58
- package/.agents/scripts/lib/signals/schema.js +1 -1
- package/.agents/scripts/lib/single-story-sweep/sweep-lock.js +284 -25
- package/.agents/scripts/lib/skills/walk-skill-files.js +1 -1
- package/.agents/scripts/lib/source-text/strip-js-comments.js +115 -0
- package/.agents/scripts/lib/story-adjacency.js +2 -2
- package/.agents/scripts/lib/templates/decomposer-prompts.js +1 -1
- package/.agents/scripts/lib/test-isolate/runner.js +3 -3
- package/.agents/scripts/lib/test-runner-contract.js +14 -6
- package/.agents/scripts/lib/test-tiers.js +135 -18
- package/.agents/scripts/lib/ticket-body-sections.js +1 -1
- package/.agents/scripts/lib/transpile.js +16 -3
- package/.agents/scripts/lib/util/concurrent-map.js +1 -1
- package/.agents/scripts/lib/util/parse-id-list.js +1 -1
- package/.agents/scripts/lib/wave-runner/live-probe.js +4 -4
- package/.agents/scripts/lib/wave-runner/ready-set.js +1 -1
- package/.agents/scripts/lib/workflow-closure.js +1 -1
- package/.agents/scripts/lib/worktree/lifecycle/pending-cleanup.js +170 -32
- package/.agents/scripts/lib/worktree/node-modules-strategy.js +2 -5
- package/.agents/scripts/lint-issue-body.js +3 -3
- package/.agents/scripts/plan-context.js +4 -4
- package/.agents/scripts/plan-critics.js +2 -2
- package/.agents/scripts/plan-persist.js +2 -2
- package/.agents/scripts/plan-run-epilogue.js +2 -2
- package/.agents/scripts/pr-watch-with-update.js +305 -137
- package/.agents/scripts/providers/github/board-add.js +1 -1
- package/.agents/scripts/providers/github/errors.js +1 -1
- package/.agents/scripts/providers/github/tickets.js +114 -21
- package/.agents/scripts/quality-preview.js +162 -70
- package/.agents/scripts/resolve-stories.js +3 -3
- package/.agents/scripts/resync-status-column.js +1 -1
- package/.agents/scripts/run-lint.js +1 -1
- package/.agents/scripts/run-test-profile.js +8 -5
- package/.agents/scripts/run-tests.js +79 -14
- package/.agents/scripts/single-story-close.js +2 -4
- package/.agents/scripts/single-story-init.js +10 -6
- package/.agents/scripts/stories-wave-tick.js +8 -8
- package/.agents/skills/core/idea-refinement/SKILL.md +5 -5
- package/.agents/skills/core/scope-triage/SKILL.md +3 -3
- package/.agents/skills/skills.index.json +1 -1
- package/.agents/skills/stack/qa/qa-harness/SKILL.md +1 -1
- package/.agents/workflows/audit-data-model.md +1 -1
- package/.agents/workflows/audit-documentation.md +2 -2
- package/.agents/workflows/audit-to-stories.md +11 -11
- package/.agents/workflows/git-deliver.md +4 -4
- package/.agents/workflows/helpers/_merge-conflict-template.md +1 -1
- package/.agents/workflows/helpers/audit-lens-core.md +1 -1
- package/.agents/workflows/helpers/code-review.md +4 -4
- package/.agents/workflows/helpers/deliver-light.md +26 -26
- package/.agents/workflows/helpers/deliver-reference.md +15 -7
- package/.agents/workflows/helpers/deliver-story-reference.md +9 -4
- package/.agents/workflows/helpers/deliver-story.md +6 -7
- package/.agents/workflows/helpers/diagnose.md +2 -2
- package/.agents/workflows/helpers/parallel-tooling.md +1 -1
- package/.agents/workflows/helpers/plan-reference.md +9 -9
- package/.agents/workflows/helpers/qa-core.md +8 -8
- package/.agents/workflows/helpers/worktree-lifecycle.md +6 -6
- package/.agents/workflows/{deliver.md → mandrel-deliver.md} +15 -19
- package/.agents/workflows/{plan.md → mandrel-plan.md} +9 -9
- package/.agents/workflows/memory-consolidate.md +4 -4
- package/.agents/workflows/prototype.md +3 -3
- package/.agents/workflows/qa-assist.md +12 -12
- package/.agents/workflows/qa-explore.md +6 -6
- package/.agents/workflows/qa-run.md +1 -1
- package/README.md +7 -7
- package/docs/CHANGELOG.md +43 -0
- package/lib/cli/doctor.js +1 -1
- package/lib/cli/init.js +1 -1
- package/lib/cli/migrate.js +100 -94
- package/lib/migrations/helpers/retire-agentrc-key.js +200 -0
- package/lib/migrations/index.js +32 -33
- package/lib/migrations/steps/2.1.0-retire-mi-drop-knobs.js +21 -80
- package/lib/migrations/steps/2.1.0-retire-verify-concurrency-cap.js +15 -87
- package/lib/migrations/steps/2.11.0-retire-max-seed-words.js +13 -71
- package/lib/migrations/steps/2.2.0-retire-epic-ac-tags.js +1 -1
- package/lib/migrations/steps/2.20.0-retire-codebase-snapshot.js +13 -101
- package/lib/migrations/steps/2.32.0-retire-lint-baseline-command.js +13 -100
- package/package.json +7 -3
- package/.agents/scripts/lib/audit-suite/frontmatter-lint.js +0 -32
- package/.agents/scripts/lib/baselines/maintainability-baseline-save.js +0 -37
- package/.agents/scripts/lib/cli/parse-numeric.js +0 -60
- package/.agents/scripts/lib/close-validation/telemetry.js +0 -79
- package/.agents/scripts/lib/orchestration/label-transitions.js +0 -44
- package/.agents/scripts/lib/orchestration/parked-follow-ons.js +0 -147
- package/.agents/scripts/lib/orchestration/phase-runner.js +0 -88
- package/.agents/scripts/lib/orchestration/recut.js +0 -56
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* on a Windows lock-class failure, the entry is appended to
|
|
8
8
|
* `.worktrees/.pending-cleanup.json`. The plan-time `worktree-sweep.js`
|
|
9
9
|
* reader (shipped in Epic #349) picks up the manifest on the next
|
|
10
|
-
* `/plan` run and retries removal
|
|
10
|
+
* `/mandrel-plan` run and retries removal
|
|
11
11
|
* (`git worktree remove` then `fs.rm`) — by then the live file handles from Node / AV / the Windows
|
|
12
12
|
* Search indexer are almost always gone. If `MAX_SWEEP_ATTEMPTS` elapses
|
|
13
13
|
* without clearing, an `OPERATOR ACTION REQUIRED: persistent-lock` line
|
|
@@ -15,14 +15,39 @@
|
|
|
15
15
|
*
|
|
16
16
|
* The manifest itself lives under `.worktrees/`, which is already
|
|
17
17
|
* git-ignored; no tracked state is mutated.
|
|
18
|
+
*
|
|
19
|
+
* **Story #5112 — the manifest is shared mutable state.** `recordPendingCleanup`
|
|
20
|
+
* runs from the close reap and `drainPendingCleanup` runs from the `/mandrel-plan`
|
|
21
|
+
* boot, so two concurrent delivery sessions can read-modify-write the same
|
|
22
|
+
* file. Three properties close that:
|
|
23
|
+
*
|
|
24
|
+
* 1. **Atomic write.** Every write lands in a pid-scoped temp file and is
|
|
25
|
+
* then `rename`d over the manifest, so a reader never observes a half-
|
|
26
|
+
* written array (`readManifest` treats a torn or absent file as empty,
|
|
27
|
+
* which used to silently *drop* the backlog).
|
|
28
|
+
* 2. **Mutual exclusion.** Read-modify-write runs under the shared
|
|
29
|
+
* `acquireSweepLock` primitive on a manifest-adjacent lockfile.
|
|
30
|
+
* 3. **Merge before write.** The drain holds its snapshot across awaited
|
|
31
|
+
* removals, so it re-reads and merges by `storyId` before its final
|
|
32
|
+
* write — an entry recorded mid-drain survives instead of being erased
|
|
33
|
+
* by a stale snapshot.
|
|
18
34
|
*/
|
|
19
35
|
|
|
20
36
|
import fs from 'node:fs';
|
|
21
37
|
import { rm as fsPromisesRm } from 'node:fs/promises';
|
|
22
38
|
import path from 'node:path';
|
|
23
39
|
import { NOOP_LOGGER } from '../../Logger.js';
|
|
40
|
+
import { acquireSweepLock } from '../../single-story-sweep/sweep-lock.js';
|
|
24
41
|
|
|
25
42
|
const MANIFEST_FILENAME = '.pending-cleanup.json';
|
|
43
|
+
const MANIFEST_LOCK_FILENAME = '.pending-cleanup.lock';
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Staleness threshold for the manifest lock. Deliberately short: the guarded
|
|
47
|
+
* section is a read, an in-memory merge and a rename — milliseconds — so a
|
|
48
|
+
* lockfile older than this is a crashed holder, not a slow one.
|
|
49
|
+
*/
|
|
50
|
+
const MANIFEST_LOCK_TIMEOUT_MS = 5_000;
|
|
26
51
|
/**
|
|
27
52
|
* After a reap hands off to the manifest, `attempts` counts failed
|
|
28
53
|
* `drainPendingCleanup` passes (initial hand-off uses `attempts: 0`). The entry
|
|
@@ -35,6 +60,49 @@ export function manifestPath(worktreeRoot) {
|
|
|
35
60
|
return path.join(worktreeRoot, MANIFEST_FILENAME);
|
|
36
61
|
}
|
|
37
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Path of the lockfile guarding manifest read-modify-write. Manifest-adjacent
|
|
65
|
+
* (same gitignored `.worktrees/` directory) so it needs no extra config and
|
|
66
|
+
* shares the manifest's lifetime. Module-private: the lock is internal to
|
|
67
|
+
* this manifest's read-modify-write, and callers only ever observe that a
|
|
68
|
+
* mutation completed and left nothing behind.
|
|
69
|
+
*
|
|
70
|
+
* @param {string} worktreeRoot
|
|
71
|
+
* @returns {string}
|
|
72
|
+
*/
|
|
73
|
+
function manifestLockPath(worktreeRoot) {
|
|
74
|
+
return path.join(worktreeRoot, MANIFEST_LOCK_FILENAME);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Run `fn` under the manifest lock. Best-effort in the same sense as every
|
|
79
|
+
* other use of this primitive: on contention the work still runs (the
|
|
80
|
+
* merge-before-write below is what makes a lost race non-destructive), so a
|
|
81
|
+
* stuck lockfile can never wedge a reap. Always releases.
|
|
82
|
+
*
|
|
83
|
+
* @template T
|
|
84
|
+
* @param {string} worktreeRoot
|
|
85
|
+
* @param {() => T} fn
|
|
86
|
+
* @returns {T}
|
|
87
|
+
*/
|
|
88
|
+
function withManifestLock(worktreeRoot, fn) {
|
|
89
|
+
const lock = acquireSweepLock({
|
|
90
|
+
lockPath: manifestLockPath(worktreeRoot),
|
|
91
|
+
timeoutMs: MANIFEST_LOCK_TIMEOUT_MS,
|
|
92
|
+
});
|
|
93
|
+
try {
|
|
94
|
+
return fn();
|
|
95
|
+
} finally {
|
|
96
|
+
if (lock.acquired) {
|
|
97
|
+
try {
|
|
98
|
+
lock.release();
|
|
99
|
+
} catch {
|
|
100
|
+
// Release is best-effort; a stale lockfile expires on its own.
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
38
106
|
export function readManifest(worktreeRoot) {
|
|
39
107
|
const p = manifestPath(worktreeRoot);
|
|
40
108
|
if (!fs.existsSync(p)) return [];
|
|
@@ -47,6 +115,13 @@ export function readManifest(worktreeRoot) {
|
|
|
47
115
|
}
|
|
48
116
|
}
|
|
49
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Write the manifest atomically: serialize into a pid-scoped temp file in the
|
|
120
|
+
* same directory (so `rename` stays within one filesystem and is therefore
|
|
121
|
+
* atomic), then rename it over the manifest. A concurrent reader sees either
|
|
122
|
+
* the whole previous manifest or the whole new one — never a truncated array
|
|
123
|
+
* that `readManifest`'s catch would silently turn into "no pending cleanups".
|
|
124
|
+
*/
|
|
50
125
|
function writeManifest(worktreeRoot, entries) {
|
|
51
126
|
const p = manifestPath(worktreeRoot);
|
|
52
127
|
if (!Array.isArray(entries) || entries.length === 0) {
|
|
@@ -58,7 +133,37 @@ function writeManifest(worktreeRoot, entries) {
|
|
|
58
133
|
return;
|
|
59
134
|
}
|
|
60
135
|
fs.mkdirSync(worktreeRoot, { recursive: true });
|
|
61
|
-
|
|
136
|
+
writeFileAtomic(p, `${JSON.stringify(entries, null, 2)}\n`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Write `contents` to `targetPath` atomically: serialize into a pid-scoped
|
|
141
|
+
* temp file beside it (same directory, so the rename stays within one
|
|
142
|
+
* filesystem and is therefore atomic), then rename it into place. On failure
|
|
143
|
+
* the temp file is reaped and the error propagates — a half-written file is
|
|
144
|
+
* never left where a reader could pick it up.
|
|
145
|
+
*
|
|
146
|
+
* @param {string} targetPath
|
|
147
|
+
* @param {string} contents
|
|
148
|
+
*/
|
|
149
|
+
function writeFileAtomic(targetPath, contents) {
|
|
150
|
+
const tmp = `${targetPath}.${process.pid}.tmp`;
|
|
151
|
+
try {
|
|
152
|
+
fs.writeFileSync(tmp, contents, 'utf8');
|
|
153
|
+
fs.renameSync(tmp, targetPath);
|
|
154
|
+
} catch (err) {
|
|
155
|
+
reapTempFile(tmp);
|
|
156
|
+
throw err;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Best-effort removal of an abandoned atomic-write temp file. */
|
|
161
|
+
function reapTempFile(tmp) {
|
|
162
|
+
try {
|
|
163
|
+
fs.unlinkSync(tmp);
|
|
164
|
+
} catch {
|
|
165
|
+
// Temp file already gone (or never created) — nothing to reap.
|
|
166
|
+
}
|
|
62
167
|
}
|
|
63
168
|
|
|
64
169
|
/**
|
|
@@ -71,38 +176,45 @@ export function recordPendingCleanup(
|
|
|
71
176
|
worktreeRoot,
|
|
72
177
|
{ storyId, branch, path: wtPath, push = false },
|
|
73
178
|
) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
push
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
179
|
+
// The read, the merge and the write are one critical section: without the
|
|
180
|
+
// lock a concurrent drain's write can land between our read and our write
|
|
181
|
+
// and erase this hand-off entirely.
|
|
182
|
+
return withManifestLock(worktreeRoot, () => {
|
|
183
|
+
const now = new Date().toISOString();
|
|
184
|
+
const entries = readManifest(worktreeRoot);
|
|
185
|
+
const idx = entries.findIndex((e) => e.storyId === storyId);
|
|
186
|
+
if (idx >= 0) {
|
|
187
|
+
entries[idx] = {
|
|
188
|
+
...entries[idx],
|
|
189
|
+
branch,
|
|
190
|
+
path: wtPath,
|
|
191
|
+
push,
|
|
192
|
+
lastFailedAt: now,
|
|
193
|
+
attempts: (entries[idx].attempts ?? 0) + 1,
|
|
194
|
+
};
|
|
195
|
+
} else {
|
|
196
|
+
entries.push({
|
|
197
|
+
storyId,
|
|
198
|
+
branch,
|
|
199
|
+
path: wtPath,
|
|
200
|
+
push,
|
|
201
|
+
firstFailedAt: now,
|
|
202
|
+
lastFailedAt: now,
|
|
203
|
+
attempts: 0,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
writeManifest(worktreeRoot, entries);
|
|
207
|
+
return entries.find((e) => e.storyId === storyId);
|
|
208
|
+
});
|
|
99
209
|
}
|
|
100
210
|
|
|
101
211
|
export function removePendingCleanup(worktreeRoot, storyId) {
|
|
102
|
-
|
|
103
|
-
(
|
|
104
|
-
|
|
105
|
-
|
|
212
|
+
withManifestLock(worktreeRoot, () => {
|
|
213
|
+
const entries = readManifest(worktreeRoot).filter(
|
|
214
|
+
(e) => e.storyId !== storyId,
|
|
215
|
+
);
|
|
216
|
+
writeManifest(worktreeRoot, entries);
|
|
217
|
+
});
|
|
106
218
|
}
|
|
107
219
|
|
|
108
220
|
async function removeStuckWorktreePath(wtPath, { git, repoRoot, fsRm }) {
|
|
@@ -176,6 +288,32 @@ async function retryStage1ForEntry(entry, ctx) {
|
|
|
176
288
|
return { success: true, ...cleanup };
|
|
177
289
|
}
|
|
178
290
|
|
|
291
|
+
/**
|
|
292
|
+
* Write the drain's result, merged against whatever the manifest holds *now*.
|
|
293
|
+
*
|
|
294
|
+
* The drain awaits a removal per entry, so its `entries` snapshot can be
|
|
295
|
+
* minutes old by the time it writes. Writing that snapshot back wholesale
|
|
296
|
+
* erased any entry a concurrent `recordPendingCleanup` added mid-drain — the
|
|
297
|
+
* exact hand-off that reap had just decided it could not complete. Merging by
|
|
298
|
+
* `storyId` keeps it: rows this pass re-computed win, rows it successfully
|
|
299
|
+
* drained are dropped, and everything else the manifest has gained survives.
|
|
300
|
+
*
|
|
301
|
+
* @param {string} worktreeRoot
|
|
302
|
+
* @param {object[]} next Rows this drain re-computed (still pending).
|
|
303
|
+
* @param {Set<number|string>} drainedIds Story ids this drain cleared.
|
|
304
|
+
*/
|
|
305
|
+
function commitDrainedManifest(worktreeRoot, next, drainedIds) {
|
|
306
|
+
withManifestLock(worktreeRoot, () => {
|
|
307
|
+
const byId = new Map(next.map((entry) => [entry.storyId, entry]));
|
|
308
|
+
for (const entry of readManifest(worktreeRoot)) {
|
|
309
|
+
if (byId.has(entry.storyId)) continue;
|
|
310
|
+
if (drainedIds.has(entry.storyId)) continue;
|
|
311
|
+
byId.set(entry.storyId, entry);
|
|
312
|
+
}
|
|
313
|
+
writeManifest(worktreeRoot, [...byId.values()]);
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
|
|
179
317
|
/**
|
|
180
318
|
* Drain the pending-cleanup manifest: for each entry, retry Stage 1
|
|
181
319
|
* cleanup. Successful entries are removed; failing entries have their
|
|
@@ -252,7 +390,7 @@ export async function drainPendingCleanup({
|
|
|
252
390
|
next.push(updated);
|
|
253
391
|
}
|
|
254
392
|
|
|
255
|
-
|
|
393
|
+
commitDrainedManifest(worktreeRoot, next, new Set(drained));
|
|
256
394
|
return {
|
|
257
395
|
drained,
|
|
258
396
|
drainedDetails,
|
|
@@ -401,10 +401,7 @@ export function describeAttemptFailure(result, timeoutMs) {
|
|
|
401
401
|
}
|
|
402
402
|
|
|
403
403
|
/** Relative path of the per-machine pnpm-store prime sentinel (under tempRoot). */
|
|
404
|
-
|
|
405
|
-
'temp',
|
|
406
|
-
'.pnpm-store-primed',
|
|
407
|
-
);
|
|
404
|
+
const PNPM_STORE_PRIME_SENTINEL = path.join('temp', '.pnpm-store-primed');
|
|
408
405
|
|
|
409
406
|
/**
|
|
410
407
|
* Pure: one-time per-machine pnpm content-addressable-store prime.
|
|
@@ -424,7 +421,7 @@ export const PNPM_STORE_PRIME_SENTINEL = path.join(
|
|
|
424
421
|
*
|
|
425
422
|
* @returns {{ primed: 'primed' | 'cached' | 'failed' | 'skipped', reason?: string }}
|
|
426
423
|
*/
|
|
427
|
-
|
|
424
|
+
function primePnpmStore({
|
|
428
425
|
strategy,
|
|
429
426
|
repoRoot,
|
|
430
427
|
logger,
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
* human files a ticket whose body `parse()` rejects (or which lacks the
|
|
11
11
|
* binding `goal` / `acceptance` / `verify` sections), the lint surfaces a
|
|
12
12
|
* **comment** on the issue rather than failing silently — the supported
|
|
13
|
-
* human entry points (`/plan` from an existing Epic ID, the qa-assist →
|
|
14
|
-
* `/plan` handoff) depend on a parseable body.
|
|
13
|
+
* human entry points (`/mandrel-plan` from an existing Epic ID, the qa-assist →
|
|
14
|
+
* `/mandrel-plan` handoff) depend on a parseable body.
|
|
15
15
|
*
|
|
16
16
|
* ## Design
|
|
17
17
|
*
|
|
@@ -157,7 +157,7 @@ export function renderConformanceComment(verdict) {
|
|
|
157
157
|
'### ⚠️ Ticket body does not round-trip through the Mandrel parser',
|
|
158
158
|
'',
|
|
159
159
|
'Agents build ticket bodies from a canonical schema that this body does ' +
|
|
160
|
-
'not match, so the supported human entry points (e.g. `/plan` from an ' +
|
|
160
|
+
'not match, so the supported human entry points (e.g. `/mandrel-plan` from an ' +
|
|
161
161
|
'existing Epic ID) will reject it. Please fix the following:',
|
|
162
162
|
'',
|
|
163
163
|
...verdict.problems.map((p) => `- ${p}`),
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
/* node:coverage ignore file */
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* plan-context.js — step 1 of the collapsed `/plan` pipeline.
|
|
5
|
+
* plan-context.js — step 1 of the collapsed `/mandrel-plan` pipeline.
|
|
6
6
|
*
|
|
7
|
-
* Emits one stdout-pure JSON envelope for the `/plan` authoring middle.
|
|
7
|
+
* Emits one stdout-pure JSON envelope for the `/mandrel-plan` authoring middle.
|
|
8
8
|
*
|
|
9
9
|
* Two operator modes (exactly one is required):
|
|
10
10
|
*
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
*
|
|
26
26
|
* Flags:
|
|
27
27
|
* --out <path> Write the envelope to <path> (parent dirs created).
|
|
28
|
-
* `/plan` points this at `<plan-dir>/plan-context.json`,
|
|
28
|
+
* `/mandrel-plan` points this at `<plan-dir>/plan-context.json`,
|
|
29
29
|
* which is where `plan-persist.js` auto-discovers the
|
|
30
30
|
* `--tickets` source ids from (Story #4554). Without a
|
|
31
31
|
* captured envelope persist cannot know a `--tickets` run
|
|
@@ -348,7 +348,7 @@ runAsCli(import.meta.url, main, {
|
|
|
348
348
|
invocation:
|
|
349
349
|
'node .agents/scripts/plan-context.js (--seed "<text>" | --seed-file <path> | --tickets <ids> | --amends <id>) [--out <path>] [--pretty]',
|
|
350
350
|
summary:
|
|
351
|
-
'Build the /plan authoring-context envelope on stdout. Exactly one entry form must be supplied.',
|
|
351
|
+
'Build the /mandrel-plan authoring-context envelope on stdout. Exactly one entry form must be supplied.',
|
|
352
352
|
flags: [
|
|
353
353
|
['--seed "<text>"', 'Inline seed prose.'],
|
|
354
354
|
['--seed-file <path>', 'Seed document to read.'],
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* plan-critics.js — the /plan critic-dispatch verdict CLI (Story #4592).
|
|
4
|
+
* plan-critics.js — the /mandrel-plan critic-dispatch verdict CLI (Story #4592).
|
|
5
5
|
*
|
|
6
|
-
* `/plan` step 2.5 (between Author and Persist) runs this against the draft
|
|
6
|
+
* `/mandrel-plan` step 2.5 (between Author and Persist) runs this against the draft
|
|
7
7
|
* `stories.json`. It evaluates the consolidation + pre-mortem dispatch
|
|
8
8
|
* conditions and prints the verdict as JSON on stdout so the workflow can
|
|
9
9
|
* act on it — dispatching a fresh-context critic sub-agent and folding its
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* plan-persist.js — flat Story GitHub-write surface for v2 `/plan`
|
|
4
|
+
* plan-persist.js — flat Story GitHub-write surface for v2 `/mandrel-plan`
|
|
5
5
|
* (Stage 3 — `docs/roadmap.md`).
|
|
6
6
|
*
|
|
7
7
|
* Given the author-written planning artifacts (`stories.json`, optional shared
|
|
@@ -201,7 +201,7 @@ async function loadArtifacts(paths) {
|
|
|
201
201
|
*
|
|
202
202
|
* Exported for tests: this is the join where the envelope-derived source ids
|
|
203
203
|
* meet the persist engine, so a regression here silently un-wires
|
|
204
|
-
* `/plan --tickets` superseding (Story #4554).
|
|
204
|
+
* `/mandrel-plan --tickets` superseding (Story #4554).
|
|
205
205
|
*
|
|
206
206
|
* @param {object} values Parsed `parseArgs` values.
|
|
207
207
|
* @param {ReturnType<typeof resolveInputPaths>} paths
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* plan-run-epilogue.js — execute the real per-run closeout for a
|
|
4
|
-
* multi-Story `/deliver`.
|
|
4
|
+
* multi-Story `/mandrel-deliver`.
|
|
5
5
|
*
|
|
6
6
|
* Usage:
|
|
7
7
|
* node .agents/scripts/plan-run-epilogue.js --stories 1,2,3
|
|
@@ -67,7 +67,7 @@ export async function main(argv = process.argv.slice(2), deps = {}) {
|
|
|
67
67
|
// along with the label itself. The epilogue is keyed on the delivered id
|
|
68
68
|
// set, and the synthesized `adhoc-<ids>` id it already used for positional
|
|
69
69
|
// runs is now the only id it needs.
|
|
70
|
-
// Range tokens expand here too (`--stories 101-104`): /deliver blesses the
|
|
70
|
+
// Range tokens expand here too (`--stories 101-104`): /mandrel-deliver blesses the
|
|
71
71
|
// dash range at the operator surface, so the id set the epilogue is keyed on
|
|
72
72
|
// must read the same shape. Rejecting a bad token is deliberate — the old
|
|
73
73
|
// silent filter turned a typo into an empty, wrongly-keyed rollup.
|