mandrel 2.24.0 → 2.26.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/agents/acceptance-critic.md +19 -21
- package/.agents/agents/auditor.md +16 -19
- package/.agents/agents/story-worker.md +41 -54
- package/.agents/audit-checklists/architecture.md +5 -1
- package/.agents/audit-checklists/baselines.md +21 -0
- package/.agents/audit-checklists/quality.md +3 -0
- package/.agents/docs/agentrc-reference.json +2 -1
- package/.agents/docs/configuration.md +2 -1
- package/.agents/docs/quality-gates.md +80 -18
- package/.agents/docs/workflows.md +4 -2
- package/.agents/instructions.md +1 -1
- package/.agents/rules/ci-remediation.md +68 -3
- package/.agents/schemas/agentrc.schema.json +6 -1
- package/.agents/schemas/audit-rules.json +15 -0
- package/.agents/schemas/baselines/audit-baselines-envelope.schema.json +242 -0
- package/.agents/schemas/baselines/baseline-envelope.schema.json +8 -0
- package/.agents/schemas/baselines/crap.schema.json +13 -1
- package/.agents/schemas/crap-report.schema.json +37 -0
- package/.agents/schemas/model-attribution.schema.json +4 -0
- package/.agents/scripts/acceptance-eval.js +124 -15
- package/.agents/scripts/audit-baselines.js +136 -0
- package/.agents/scripts/audit-labels-bootstrap.js +16 -95
- package/.agents/scripts/audit-to-stories.js +33 -3
- package/.agents/scripts/check-arch-cycles.js +12 -93
- package/.agents/scripts/check-baseline-drift.js +16 -3
- package/.agents/scripts/check-baselines.js +19 -3
- package/.agents/scripts/check-context-budget.js +95 -14
- package/.agents/scripts/check-cyclomatic.js +214 -0
- package/.agents/scripts/check-schema-references.js +392 -0
- package/.agents/scripts/check-test-temp-hygiene.js +121 -1
- package/.agents/scripts/check-workflow-timeouts.js +291 -0
- package/.agents/scripts/cleanup-repo-test-temp.js +7 -5
- package/.agents/scripts/deliver-light.js +3 -4
- package/.agents/scripts/deliver-recover.js +13 -0
- package/.agents/scripts/diagnose-friction.js +85 -19
- package/.agents/scripts/lib/audit-baselines/engine.js +177 -0
- package/.agents/scripts/lib/audit-baselines/gate-surface.js +63 -0
- package/.agents/scripts/lib/audit-baselines/headroom.js +72 -0
- package/.agents/scripts/lib/audit-baselines/hotspots.js +69 -0
- package/.agents/scripts/lib/audit-baselines/kinds.js +313 -0
- package/.agents/scripts/lib/audit-baselines/outliers.js +100 -0
- package/.agents/scripts/lib/audit-baselines/read.js +87 -0
- package/.agents/scripts/lib/audit-baselines/staleness.js +123 -0
- package/.agents/scripts/lib/audit-baselines/surface-entry.js +106 -0
- package/.agents/scripts/lib/audit-baselines/trend.js +125 -0
- package/.agents/scripts/lib/audit-baselines/weights.js +193 -0
- package/.agents/scripts/lib/audit-suite/index.js +0 -5
- package/.agents/scripts/lib/audit-suite/selector.js +9 -62
- package/.agents/scripts/lib/audit-to-stories/audit-label-taxonomy.js +177 -0
- package/.agents/scripts/lib/audit-to-stories/audit-lenses.js +1 -0
- package/.agents/scripts/lib/audit-to-stories/build-story-body.js +35 -3
- package/.agents/scripts/lib/audit-to-stories/group-findings.js +20 -11
- package/.agents/scripts/lib/audit-to-stories/parse-audit-md.js +23 -9
- package/.agents/scripts/lib/audit-to-stories/seed-from-findings.js +9 -2
- package/.agents/scripts/lib/baseline-loader.js +0 -0
- package/.agents/scripts/lib/baseline-schema-registry.js +13 -1
- package/.agents/scripts/lib/baselines/diff-scope-cli.js +22 -160
- package/.agents/scripts/lib/baselines/duplication-scanner.js +27 -0
- package/.agents/scripts/lib/baselines/git-base.js +0 -0
- package/.agents/scripts/lib/baselines/kinds/crap.js +499 -57
- package/.agents/scripts/lib/baselines/preview-gates.js +58 -15
- package/.agents/scripts/lib/baselines/reader.js +10 -0
- package/.agents/scripts/lib/baselines/refresh-service.js +69 -11
- package/.agents/scripts/lib/baselines/scope.js +39 -90
- package/.agents/scripts/lib/baselines/writer.js +16 -7
- package/.agents/scripts/lib/changed-files.js +8 -1
- package/.agents/scripts/lib/cli-args.js +115 -1
- package/.agents/scripts/lib/close-validation/runner.js +70 -25
- package/.agents/scripts/lib/config-settings-schema-delivery.js +5 -0
- package/.agents/scripts/lib/crap-engine.js +124 -27
- package/.agents/scripts/lib/crap-method-identity.js +153 -0
- package/.agents/scripts/lib/crap-utils.js +86 -13
- package/.agents/scripts/lib/cyclomatic-ceiling.js +265 -0
- package/.agents/scripts/lib/env-loader.js +46 -16
- package/.agents/scripts/lib/feedback-loop/audit-results-graduator.js +0 -2
- package/.agents/scripts/lib/feedback-loop/prior-feedback-fetcher.js +0 -2
- package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +0 -2
- package/.agents/scripts/lib/findings/route-finding.js +123 -17
- package/.agents/scripts/lib/findings/severity.js +80 -2
- package/.agents/scripts/lib/git/cached-fetch.js +0 -0
- package/.agents/scripts/lib/git-utils.js +136 -80
- package/.agents/scripts/lib/import-graph.js +156 -0
- package/.agents/scripts/lib/label-constants.js +17 -0
- package/.agents/scripts/lib/observability/runtime-friction.js +17 -2
- package/.agents/scripts/lib/observability/source-classifier.js +175 -2
- package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +110 -17
- package/.agents/scripts/lib/orchestration/ceremony-routing.js +17 -12
- package/.agents/scripts/lib/orchestration/check-baselines/phases/compare.js +36 -6
- package/.agents/scripts/lib/orchestration/check-baselines/phases/evaluate.js +5 -0
- package/.agents/scripts/lib/orchestration/check-baselines/phases/floors.js +12 -1
- package/.agents/scripts/lib/orchestration/check-baselines/phases/report.js +8 -1
- package/.agents/scripts/lib/orchestration/ci-rerun-guard.js +548 -0
- package/.agents/scripts/lib/orchestration/deliver-recover.js +2 -2
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/phase-drivers.js +10 -5
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/render.js +39 -3
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +71 -20
- package/.agents/scripts/lib/orchestration/light-backstop.js +20 -3
- package/.agents/scripts/lib/orchestration/light-escalation.js +82 -3
- package/.agents/scripts/lib/orchestration/light-suitability.js +103 -5
- package/.agents/scripts/lib/orchestration/plan-context.js +119 -66
- package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +31 -5
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +216 -109
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +61 -13
- package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +79 -22
- package/.agents/scripts/lib/orchestration/plan-text-hygiene.js +51 -20
- package/.agents/scripts/lib/orchestration/planning/authoring-context.js +70 -74
- package/.agents/scripts/lib/orchestration/planning/memory-pool-advisory.js +231 -0
- package/.agents/scripts/lib/orchestration/resolve-stories.js +18 -17
- package/.agents/scripts/lib/orchestration/retro-proposals.js +0 -0
- package/.agents/scripts/lib/orchestration/run-epilogue.js +12 -0
- package/.agents/scripts/lib/orchestration/run-scoped-config.js +276 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +41 -10
- package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +125 -37
- package/.agents/scripts/lib/orchestration/single-story-close/phases/normalize-pr-title.js +6 -6
- package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +42 -38
- package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +129 -21
- package/.agents/scripts/lib/orchestration/single-story-close/phases/push.js +6 -1
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +267 -125
- package/.agents/scripts/lib/orchestration/spec-budget.js +16 -5
- package/.agents/scripts/lib/orchestration/story-follow-ups.js +182 -95
- package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +22 -0
- package/.agents/scripts/lib/orchestration/ticket-validator.js +5 -11
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +4 -4
- package/.agents/scripts/lib/reserved-test-ids.js +77 -0
- package/.agents/scripts/lib/story-adjacency.js +3 -3
- package/.agents/scripts/lib/story-plan.js +137 -42
- package/.agents/scripts/lib/templates/decomposer-prompts.js +4 -4
- package/.agents/scripts/lib/test-runner-contract.js +134 -0
- package/.agents/scripts/lib/test-tiers.js +11 -2
- package/.agents/scripts/lib/ticket-body-sections.js +1 -1
- package/.agents/scripts/lib/util/concurrent-map.js +17 -0
- package/.agents/scripts/lib/util/parse-id-list.js +103 -0
- package/.agents/scripts/lib/util/poll-loop.js +18 -2
- package/.agents/scripts/lib/wave-runner/live-probe.js +29 -13
- package/.agents/scripts/lib/wave-runner/ready-set.js +254 -43
- package/.agents/scripts/lib/workers/combined-mi-crap-worker.js +10 -13
- package/.agents/scripts/lib/workers/crap-worker.js +8 -13
- package/.agents/scripts/lib/workers/maintainability-report-worker.js +4 -10
- package/.agents/scripts/lib/workers/maintainability-worker.js +4 -10
- package/.agents/scripts/lib/workers/serve-worker-messages.js +35 -0
- package/.agents/scripts/lib/worktree/git-hooks.js +206 -0
- package/.agents/scripts/lib/worktree/lifecycle/creation.js +6 -0
- package/.agents/scripts/lib/worktree-manager.js +14 -0
- package/.agents/scripts/plan-run-epilogue.js +17 -5
- package/.agents/scripts/pr-watch-with-update.js +568 -213
- package/.agents/scripts/providers/github/tickets.js +33 -10
- package/.agents/scripts/provision-git-hooks.js +85 -0
- package/.agents/scripts/quality-preview.js +137 -28
- package/.agents/scripts/resolve-stories.js +4 -1
- package/.agents/scripts/run-coverage.js +86 -35
- package/.agents/scripts/run-lint.js +20 -0
- package/.agents/scripts/run-tests.js +36 -36
- package/.agents/scripts/single-story-close.js +34 -2
- package/.agents/scripts/single-story-confirm-merge.js +22 -6
- package/.agents/scripts/single-story-init.js +7 -0
- package/.agents/scripts/stories-wave-tick.js +308 -47
- package/.agents/scripts/story-plan.js +65 -9
- package/.agents/scripts/update-coverage-baseline.js +34 -4
- package/.agents/scripts/update-crap-baseline.js +42 -4
- package/.agents/scripts/update-duplication-baseline.js +209 -83
- package/.agents/scripts/update-maintainability-baseline.js +31 -4
- package/.agents/scripts/validate-docs-freshness.js +1 -0
- package/.agents/skills/core/diagnose-friction/SKILL.md +4 -1
- package/.agents/skills/core/gates-and-baselines/SKILL.md +17 -11
- package/.agents/skills/skills.index.json +2 -2
- package/.agents/templates/single-story-body.md +16 -8
- package/.agents/workflows/audit-architecture.md +69 -13
- package/.agents/workflows/audit-baselines.md +289 -0
- package/.agents/workflows/audit-navigability.md +5 -4
- package/.agents/workflows/audit-quality.md +26 -0
- package/.agents/workflows/audit-to-stories.md +30 -4
- package/.agents/workflows/deliver.md +92 -97
- package/.agents/workflows/helpers/acceptance-self-eval.md +47 -10
- package/.agents/workflows/helpers/audit-lens-core.md +25 -4
- package/.agents/workflows/helpers/code-quality-guardrails.md +9 -2
- package/.agents/workflows/helpers/deliver-digest.md +41 -21
- package/.agents/workflows/helpers/deliver-reference.md +95 -4
- package/.agents/workflows/helpers/deliver-story-reference.md +174 -52
- package/.agents/workflows/helpers/deliver-story.md +104 -119
- package/.agents/workflows/helpers/plan-reference.md +49 -5
- package/.agents/workflows/memory-consolidate.md +116 -0
- package/.agents/workflows/plan.md +62 -75
- package/README.md +13 -6
- package/docs/CHANGELOG.md +93 -0
- package/lib/cli/update.js +14 -11
- package/lib/cli/version-check.js +9 -1
- package/package.json +9 -4
- package/.agents/schemas/friction-event.schema.json +0 -56
- package/.agents/scripts/lib/feedback-loop/memory-freshness.js +0 -707
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* worktree/git-hooks.js
|
|
3
|
+
*
|
|
4
|
+
* Materialize the repository's git hooks directory into a linked worktree.
|
|
5
|
+
*
|
|
6
|
+
* `core.hooksPath` is resolved by git against **each working tree's own
|
|
7
|
+
* root**, not against the common git dir. A relative value therefore points
|
|
8
|
+
* at a directory that only exists in the checkout that generated it — husky
|
|
9
|
+
* writes `.husky/_` from its `prepare` script in the main checkout and
|
|
10
|
+
* self-ignores it — so every linked worktree resolves the hooks path to a
|
|
11
|
+
* directory that is not there. Git finds no hooks and proceeds silently:
|
|
12
|
+
* `commit-msg`, `pre-commit` and `pre-push` do not run for any commit made in
|
|
13
|
+
* a worktree, without anyone passing a bypass flag.
|
|
14
|
+
*
|
|
15
|
+
* This module closes that gap by copying the resolved hooks directory into
|
|
16
|
+
* the worktree at the same relative path. It is deliberately total about the
|
|
17
|
+
* cases where there is nothing to do — an unset or absolute `core.hooksPath`,
|
|
18
|
+
* or a source directory that does not exist — because a consumer project
|
|
19
|
+
* without husky must not fail worktree creation. Every other outcome either
|
|
20
|
+
* materializes the hooks or throws: a silent skip is the defect being fixed.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { execFileSync } from 'node:child_process';
|
|
24
|
+
import fs from 'node:fs';
|
|
25
|
+
import path from 'node:path';
|
|
26
|
+
import * as defaultGit from '../git-utils.js';
|
|
27
|
+
import { assertPathContainment } from '../path-security.js';
|
|
28
|
+
|
|
29
|
+
// Skip reasons. Deliberately not exported: they are part of the result
|
|
30
|
+
// digest's observable contract, so callers and tests read them as the literal
|
|
31
|
+
// strings they are printed as, not through a symbol that could be renamed
|
|
32
|
+
// without anyone noticing the digest changed.
|
|
33
|
+
//
|
|
34
|
+
// hooks-path-unset — git uses the common `hooks` dir, already shared.
|
|
35
|
+
// hooks-path-absolute — resolves alike from every working tree already.
|
|
36
|
+
// source-absent — no hooks directory to mirror (no husky in this project).
|
|
37
|
+
// same-checkout — target is the source; copying it onto itself would
|
|
38
|
+
// destroy the only copy.
|
|
39
|
+
const SKIP_UNSET = 'hooks-path-unset';
|
|
40
|
+
const SKIP_ABSOLUTE = 'hooks-path-absolute';
|
|
41
|
+
const SKIP_SOURCE_ABSENT = 'source-absent';
|
|
42
|
+
const SKIP_SAME_CHECKOUT = 'same-checkout';
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Read `core.hooksPath` as configured for `repoRoot`.
|
|
46
|
+
*
|
|
47
|
+
* `git config --get` exits non-zero when the key is unset, which is a
|
|
48
|
+
* legitimate state rather than an error, so this reports `null` for both an
|
|
49
|
+
* unset key and an empty value.
|
|
50
|
+
*
|
|
51
|
+
* @param {string} repoRoot
|
|
52
|
+
* @param {{ gitSpawn: Function }} gitImpl
|
|
53
|
+
* @returns {string|null}
|
|
54
|
+
*/
|
|
55
|
+
function readHooksPath(repoRoot, gitImpl) {
|
|
56
|
+
const res = gitImpl.gitSpawn(repoRoot, 'config', '--get', 'core.hooksPath');
|
|
57
|
+
if (res.status !== 0) return null;
|
|
58
|
+
const value = (res.stdout ?? '').trim();
|
|
59
|
+
return value.length > 0 ? value : null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Canonical form of `dir` for identity comparison.
|
|
64
|
+
*
|
|
65
|
+
* `realpathSync.native` is used so a symlinked temp root (macOS `/var` →
|
|
66
|
+
* `/private/var`) does not make a checkout look distinct from itself. A path
|
|
67
|
+
* that cannot be resolved falls back to `path.resolve`, which is enough for
|
|
68
|
+
* the comparison to stay conservative.
|
|
69
|
+
*
|
|
70
|
+
* @param {string} dir
|
|
71
|
+
* @param {typeof fs} fsImpl
|
|
72
|
+
* @returns {string}
|
|
73
|
+
*/
|
|
74
|
+
function canonical(dir, fsImpl) {
|
|
75
|
+
const resolved = path.resolve(dir);
|
|
76
|
+
try {
|
|
77
|
+
return fsImpl.realpathSync.native(resolved);
|
|
78
|
+
} catch {
|
|
79
|
+
return resolved;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Names of the regular files directly inside `dir`, sorted.
|
|
85
|
+
*
|
|
86
|
+
* @param {string} dir
|
|
87
|
+
* @param {typeof fs} fsImpl
|
|
88
|
+
* @returns {string[]}
|
|
89
|
+
*/
|
|
90
|
+
function hookFileNames(dir, fsImpl) {
|
|
91
|
+
return fsImpl
|
|
92
|
+
.readdirSync(dir, { withFileTypes: true })
|
|
93
|
+
.filter((ent) => ent.isFile())
|
|
94
|
+
.map((ent) => ent.name)
|
|
95
|
+
.sort();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Materialize `repoRoot`'s resolved git hooks directory into `worktree`.
|
|
100
|
+
*
|
|
101
|
+
* Idempotent, and refreshing rather than preserving: an existing target is
|
|
102
|
+
* replaced so an upgraded husky can never leave a stale shim behind.
|
|
103
|
+
*
|
|
104
|
+
* @param {object} opts
|
|
105
|
+
* @param {string} opts.repoRoot Absolute path to the checkout that owns the hooks.
|
|
106
|
+
* @param {string} opts.worktree Absolute path to the worktree to provision.
|
|
107
|
+
* @param {{ gitSpawn: Function }} [opts.gitImpl]
|
|
108
|
+
* @param {typeof fs} [opts.fsImpl]
|
|
109
|
+
* @returns {{ action: 'materialized' | 'skipped', reason?: string,
|
|
110
|
+
* hooksPath: string|null, source: string|null, target: string|null,
|
|
111
|
+
* hooks: string[] }}
|
|
112
|
+
* @throws {Error} when the source exists but the hooks could not be placed.
|
|
113
|
+
*/
|
|
114
|
+
export function materializeGitHooks({
|
|
115
|
+
repoRoot,
|
|
116
|
+
worktree,
|
|
117
|
+
gitImpl = defaultGit,
|
|
118
|
+
fsImpl = fs,
|
|
119
|
+
} = {}) {
|
|
120
|
+
if (!repoRoot || typeof repoRoot !== 'string') {
|
|
121
|
+
throw new Error('worktree.hooks: repoRoot is required');
|
|
122
|
+
}
|
|
123
|
+
if (!worktree || typeof worktree !== 'string') {
|
|
124
|
+
throw new Error('worktree.hooks: worktree is required');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const skip = (reason, hooksPath = null) => ({
|
|
128
|
+
action: 'skipped',
|
|
129
|
+
reason,
|
|
130
|
+
hooksPath,
|
|
131
|
+
source: null,
|
|
132
|
+
target: null,
|
|
133
|
+
hooks: [],
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const hooksPath = readHooksPath(repoRoot, gitImpl);
|
|
137
|
+
if (hooksPath === null) return skip(SKIP_UNSET);
|
|
138
|
+
if (path.isAbsolute(hooksPath)) return skip(SKIP_ABSOLUTE, hooksPath);
|
|
139
|
+
|
|
140
|
+
if (canonical(repoRoot, fsImpl) === canonical(worktree, fsImpl)) {
|
|
141
|
+
return skip(SKIP_SAME_CHECKOUT, hooksPath);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const source = path.resolve(repoRoot, hooksPath);
|
|
145
|
+
if (!fsImpl.existsSync(source)) return skip(SKIP_SOURCE_ABSENT, hooksPath);
|
|
146
|
+
|
|
147
|
+
const target = path.resolve(worktree, hooksPath);
|
|
148
|
+
// `core.hooksPath` is repo configuration, but it still reaches a recursive
|
|
149
|
+
// remove below — a `../..` value must not be able to delete outside the
|
|
150
|
+
// worktree it claims to provision.
|
|
151
|
+
assertPathContainment(
|
|
152
|
+
path.resolve(worktree),
|
|
153
|
+
target,
|
|
154
|
+
'worktree.hooks: core.hooksPath',
|
|
155
|
+
{ allowEmpty: false },
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
fsImpl.rmSync(target, { recursive: true, force: true });
|
|
160
|
+
fsImpl.mkdirSync(path.dirname(target), { recursive: true });
|
|
161
|
+
fsImpl.cpSync(source, target, { recursive: true });
|
|
162
|
+
} catch (err) {
|
|
163
|
+
throw new Error(
|
|
164
|
+
`worktree.hooks: failed to materialize ${source} into ${target}: ${err.message}`,
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Verify rather than trust the copy. The whole point of this module is that
|
|
169
|
+
// a hooks directory which is absent behaves exactly like one that is
|
|
170
|
+
// present and empty — git runs neither, and says nothing either way.
|
|
171
|
+
const expected = hookFileNames(source, fsImpl);
|
|
172
|
+
const actual = new Set(
|
|
173
|
+
fsImpl.existsSync(target) ? hookFileNames(target, fsImpl) : [],
|
|
174
|
+
);
|
|
175
|
+
const missing = expected.filter((name) => !actual.has(name));
|
|
176
|
+
if (missing.length > 0) {
|
|
177
|
+
throw new Error(
|
|
178
|
+
`worktree.hooks: materialized ${target} is missing ${missing.length} hook(s): ${missing.join(', ')}`,
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return { action: 'materialized', hooksPath, source, target, hooks: expected };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Resolve the checkout that owns the shared git config from `cwd`.
|
|
187
|
+
*
|
|
188
|
+
* `--git-common-dir` reports the common git dir from any working tree —
|
|
189
|
+
* `.git` from the main checkout, an absolute path from a linked worktree —
|
|
190
|
+
* so the owning checkout is its parent. This is what lets the standalone CLI
|
|
191
|
+
* be run from inside a worktree with no arguments.
|
|
192
|
+
*
|
|
193
|
+
* @param {string} cwd
|
|
194
|
+
* @param {{ execFileSyncImpl?: typeof execFileSync }} [deps]
|
|
195
|
+
* @returns {string}
|
|
196
|
+
*/
|
|
197
|
+
export function resolveCommonCheckout(
|
|
198
|
+
cwd,
|
|
199
|
+
{ execFileSyncImpl = execFileSync } = {},
|
|
200
|
+
) {
|
|
201
|
+
const out = execFileSyncImpl('git', ['rev-parse', '--git-common-dir'], {
|
|
202
|
+
cwd,
|
|
203
|
+
encoding: 'utf8',
|
|
204
|
+
}).trim();
|
|
205
|
+
return path.dirname(path.resolve(cwd, out));
|
|
206
|
+
}
|
|
@@ -123,6 +123,12 @@ export async function ensure(ctx, storyId, branch) {
|
|
|
123
123
|
applyNodeModulesStrategy(ctx, wtPath);
|
|
124
124
|
phase('bootstrap');
|
|
125
125
|
ctx.copyBootstrapFiles(wtPath);
|
|
126
|
+
// Unconditional, and deliberately not routed through `copyBootstrapFiles`:
|
|
127
|
+
// that path is driven by `bootstrapFiles` and its provisioner copies single
|
|
128
|
+
// files, while a hooks directory is a tree. A worktree without hooks accepts
|
|
129
|
+
// commits no gate ever sees, so this runs for every worktree the manager
|
|
130
|
+
// creates and throws rather than degrading.
|
|
131
|
+
ctx.provisionGitHooks(wtPath);
|
|
126
132
|
phase('install');
|
|
127
133
|
const installStatus = installDependencies(ctx, wtPath);
|
|
128
134
|
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
DEFAULT_WORKSPACE_FILES,
|
|
26
26
|
provision as provisionWorkspace,
|
|
27
27
|
} from './workspace-provisioner.js';
|
|
28
|
+
import { materializeGitHooks } from './worktree/git-hooks.js';
|
|
28
29
|
import {
|
|
29
30
|
maybeWarnWindowsPath,
|
|
30
31
|
parseWorktreePorcelain,
|
|
@@ -161,6 +162,19 @@ export class WorktreeManager {
|
|
|
161
162
|
logger: wrapped,
|
|
162
163
|
});
|
|
163
164
|
},
|
|
165
|
+
provisionGitHooks: (wtPath) => {
|
|
166
|
+
const result = materializeGitHooks({
|
|
167
|
+
repoRoot: this.repoRoot,
|
|
168
|
+
worktree: wtPath,
|
|
169
|
+
gitImpl: this.git,
|
|
170
|
+
});
|
|
171
|
+
this.logger.info(
|
|
172
|
+
result.action === 'materialized'
|
|
173
|
+
? `worktree.bootstrap hooks materialized path=${result.target} hooks=${result.hooks.length}`
|
|
174
|
+
: `worktree.bootstrap hooks skipped reason=${result.reason}`,
|
|
175
|
+
);
|
|
176
|
+
return result;
|
|
177
|
+
},
|
|
164
178
|
};
|
|
165
179
|
}
|
|
166
180
|
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Usage:
|
|
7
7
|
* node .agents/scripts/plan-run-epilogue.js --stories 1,2,3
|
|
8
|
+
* node .agents/scripts/plan-run-epilogue.js --stories 101-104 # inclusive range
|
|
8
9
|
*
|
|
9
10
|
* Keyed on the delivered id set: an `adhoc-<sorted-ids>` run id is
|
|
10
11
|
* synthesized from `--stories`. Story #4540 retired the `--run <planRunId>`
|
|
@@ -19,6 +20,7 @@ import { resolveConfig } from './lib/config-resolver.js';
|
|
|
19
20
|
import { Logger } from './lib/Logger.js';
|
|
20
21
|
import { runPlanRunEpilogue } from './lib/orchestration/run-epilogue.js';
|
|
21
22
|
import { createProvider } from './lib/provider-factory.js';
|
|
23
|
+
import { expandIdList } from './lib/util/parse-id-list.js';
|
|
22
24
|
|
|
23
25
|
const CLI_OPTIONS = {
|
|
24
26
|
stories: { type: 'string' },
|
|
@@ -65,10 +67,17 @@ export async function main(argv = process.argv.slice(2), deps = {}) {
|
|
|
65
67
|
// along with the label itself. The epilogue is keyed on the delivered id
|
|
66
68
|
// set, and the synthesized `adhoc-<ids>` id it already used for positional
|
|
67
69
|
// runs is now the only id it needs.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
// Range tokens expand here too (`--stories 101-104`): /deliver blesses the
|
|
71
|
+
// dash range at the operator surface, so the id set the epilogue is keyed on
|
|
72
|
+
// must read the same shape. Rejecting a bad token is deliberate — the old
|
|
73
|
+
// silent filter turned a typo into an empty, wrongly-keyed rollup.
|
|
74
|
+
const { ids: stories, error: storiesError } = expandIdList(values.stories, {
|
|
75
|
+
flag: '--stories',
|
|
76
|
+
prefix: '[plan-run-epilogue] ',
|
|
77
|
+
});
|
|
78
|
+
if (storiesError) {
|
|
79
|
+
throw new Error(storiesError);
|
|
80
|
+
}
|
|
72
81
|
|
|
73
82
|
const planRunId = `adhoc-${[...stories].sort((a, b) => a - b).join('-')}`;
|
|
74
83
|
|
|
@@ -162,7 +171,10 @@ await runAsCli(import.meta.url, main, {
|
|
|
162
171
|
summary:
|
|
163
172
|
'Close out a delivery run: roll up the delivered Stories’ signals and report the run’s loop health.',
|
|
164
173
|
flags: [
|
|
165
|
-
[
|
|
174
|
+
[
|
|
175
|
+
'--stories <ids>',
|
|
176
|
+
'Comma-separated delivered Story ids, singles or A-B ranges (required).',
|
|
177
|
+
],
|
|
166
178
|
['--cwd <path>', 'Repository root (default: process cwd).'],
|
|
167
179
|
],
|
|
168
180
|
},
|