mandrel 2.37.0 → 2.39.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 +61 -24
- package/.agents/agents/plan-critic.md +1 -1
- package/.agents/docs/SDLC.md +40 -40
- package/.agents/docs/agentrc-reference.json +1 -4
- package/.agents/docs/configuration.md +18 -18
- 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/schemas/agentrc.schema.json +15 -16
- 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/check-doc-links.js +32 -4
- 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 +1 -1
- package/.agents/scripts/evidence-gate.js +1 -1
- package/.agents/scripts/generate-skills-index.js +158 -75
- 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/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 +1 -1
- package/.agents/scripts/lib/bootstrap/commit-push.js +2 -2
- package/.agents/scripts/lib/bootstrap/issue-forms-template.js +1 -1
- package/.agents/scripts/lib/changed-files.js +100 -9
- package/.agents/scripts/lib/checks/core-bare-clean.js +1 -1
- package/.agents/scripts/lib/checks/story-init-not-backgrounded.js +1 -1
- package/.agents/scripts/lib/cli-args.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/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 +28 -10
- package/.agents/scripts/lib/duplicate-search.js +1 -1
- package/.agents/scripts/lib/feedback-loop/prior-feedback-fetcher.js +4 -4
- package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +1 -1
- 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 +1 -1
- package/.agents/scripts/lib/git-branch-lifecycle.js +2 -2
- package/.agents/scripts/lib/label-constants.js +2 -2
- package/.agents/scripts/lib/label-taxonomy.js +1 -1
- 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 +16 -16
- 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/remote-verifier.js +2 -2
- package/.agents/scripts/lib/orchestration/resolve-stories.js +4 -4
- 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/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-deliver-terminal.js +14 -12
- package/.agents/scripts/lib/orchestration/ticket-validator.js +1 -1
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +3 -3
- package/.agents/scripts/lib/plan-phase-cleanup.js +1 -1
- package/.agents/scripts/lib/qa/resolve-qa-contract.js +58 -6
- package/.agents/scripts/lib/signals/schema.js +1 -1
- package/.agents/scripts/lib/skills/skills-index.js +168 -0
- package/.agents/scripts/lib/skills/walk-skill-files.js +133 -9
- package/.agents/scripts/lib/story-adjacency.js +2 -2
- package/.agents/scripts/lib/templates/decomposer-prompts.js +1 -1
- package/.agents/scripts/lib/ticket-body-sections.js +1 -1
- 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 +2 -2
- 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/providers/github/board-add.js +1 -1
- package/.agents/scripts/providers/github/errors.js +1 -1
- package/.agents/scripts/providers/github/tickets.js +6 -6
- package/.agents/scripts/quality-preview.js +50 -9
- 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/single-story-close.js +2 -2
- package/.agents/scripts/single-story-init.js +5 -5
- package/.agents/scripts/stories-wave-tick.js +8 -8
- package/.agents/scripts/validate-skills.js +53 -66
- 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 +14 -6
- package/README.md +7 -7
- package/docs/CHANGELOG.md +23 -0
- package/lib/cli/doctor.js +1 -1
- package/lib/cli/init.js +1 -1
- package/lib/migrations/steps/2.2.0-retire-epic-ac-tags.js +1 -1
- package/package.json +1 -1
|
@@ -26,11 +26,25 @@
|
|
|
26
26
|
* harness invocation — by exact name or by raw-URL origin match against each
|
|
27
27
|
* environment's `baseUrl` — and throws loudly (naming the known environments)
|
|
28
28
|
* on an unknown name or unmatched URL.
|
|
29
|
+
*
|
|
30
|
+
* It also **resolves the selected environment's `signInSeam`** (Story #5135).
|
|
31
|
+
* A `{ skill }` seam naming an id that resolves to no readable `SKILL.md`
|
|
32
|
+
* under either skills root used to fail silently: the contract validated, the
|
|
33
|
+
* seam was returned unread, and the dangling pointer only surfaced much later
|
|
34
|
+
* when a sweep reached its sign-in step — after the harness had already
|
|
35
|
+
* driven a browser. Resolution now happens here, at config-resolution time,
|
|
36
|
+
* so the failure lands where the operator can fix `.agentrc.json`. A seam
|
|
37
|
+
* that is absent entirely is a legitimate, declarable state (the workflows
|
|
38
|
+
* drive the unauthenticated surface and record the gap), not an error.
|
|
29
39
|
*/
|
|
30
40
|
|
|
31
41
|
import Ajv from 'ajv';
|
|
32
|
-
|
|
33
42
|
import { QA_SCHEMA } from '../config-settings-schema.js';
|
|
43
|
+
import { PROJECT_ROOT } from '../project-root.js';
|
|
44
|
+
import {
|
|
45
|
+
resolveSkillFile,
|
|
46
|
+
SKILL_SEARCH_ROOTS,
|
|
47
|
+
} from '../skills/walk-skill-files.js';
|
|
34
48
|
|
|
35
49
|
/**
|
|
36
50
|
* The harness-required fields. The AJV `QA_SCHEMA` keeps these optional so
|
|
@@ -242,6 +256,40 @@ function toOrigin(value) {
|
|
|
242
256
|
}
|
|
243
257
|
}
|
|
244
258
|
|
|
259
|
+
/**
|
|
260
|
+
* Normalize and verify one environment's `signInSeam`.
|
|
261
|
+
*
|
|
262
|
+
* An absent seam normalizes to `null` — a declarable state, not an error.
|
|
263
|
+
* A `{ skill }` seam is resolved against both skills roots and throws when
|
|
264
|
+
* it resolves under neither, so a dangling pointer is caught here rather
|
|
265
|
+
* than mid-sweep. The resolved `SKILL.md` path is attached as
|
|
266
|
+
* `skillPath` so the harness reads the file the check actually found.
|
|
267
|
+
*
|
|
268
|
+
* @param {object | undefined} seam
|
|
269
|
+
* @param {string} envName Environment name, for the error message.
|
|
270
|
+
* @param {{ repoRoot?: string }} options
|
|
271
|
+
* @returns {object | null}
|
|
272
|
+
*/
|
|
273
|
+
function resolveSignInSeam(seam, envName, options) {
|
|
274
|
+
if (seam == null) return null;
|
|
275
|
+
if (typeof seam.skill !== 'string') return seam;
|
|
276
|
+
|
|
277
|
+
const repoRoot = options.repoRoot ?? PROJECT_ROOT;
|
|
278
|
+
const found = resolveSkillFile(repoRoot, seam.skill);
|
|
279
|
+
if (found === null) {
|
|
280
|
+
throw new Error(
|
|
281
|
+
`qa: environment \`${envName}\` declares signInSeam.skill ` +
|
|
282
|
+
`\`${seam.skill}\`, which resolves to no readable SKILL.md. ` +
|
|
283
|
+
`Searched ${SKILL_SEARCH_ROOTS.map((r) => `\`${r}/<skill>/SKILL.md\``).join(' and ')}. ` +
|
|
284
|
+
'Author the skill under the consumer-writable `.agents/local/skills/` ' +
|
|
285
|
+
'zone (it is never pruned by `mandrel sync` and never flagged as ' +
|
|
286
|
+
'payload drift), correct the id, or omit `signInSeam` entirely if ' +
|
|
287
|
+
'this target genuinely has no sign-in seam.',
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
return { ...seam, skillPath: found.path };
|
|
291
|
+
}
|
|
292
|
+
|
|
245
293
|
/**
|
|
246
294
|
* Resolve a single QA environment for one harness invocation.
|
|
247
295
|
*
|
|
@@ -262,13 +310,17 @@ function toOrigin(value) {
|
|
|
262
310
|
* Fails **loudly**: an unknown name or an unmatched URL throws an error that
|
|
263
311
|
* names the known environments so the operator can correct the invocation.
|
|
264
312
|
*
|
|
265
|
-
* @param {{ environments: Record<string, { baseUrl: string, signInSeam
|
|
313
|
+
* @param {{ environments: Record<string, { baseUrl: string, signInSeam?: object, allowWrites?: boolean }>, defaultEnvironment: string }} contract
|
|
266
314
|
* A contract returned by `resolveQaContract`.
|
|
267
315
|
* @param {string} [target] Environment name or raw URL. Omit for the default.
|
|
268
|
-
* @
|
|
269
|
-
*
|
|
316
|
+
* @param {{ repoRoot?: string }} [options] `repoRoot` roots skill-seam
|
|
317
|
+
* resolution; defaults to the project root. Injected by tests.
|
|
318
|
+
* @returns {{ name: string, baseUrl: string, signInSeam: object | null, allowWrites: boolean }}
|
|
319
|
+
* `signInSeam` is `null` when the environment declares none.
|
|
320
|
+
* @throws {Error} on an unknown name, an unmatched URL, or a `{ skill }` seam
|
|
321
|
+
* that resolves under no skills root.
|
|
270
322
|
*/
|
|
271
|
-
export function resolveQaEnvironment(contract, target) {
|
|
323
|
+
export function resolveQaEnvironment(contract, target, options = {}) {
|
|
272
324
|
const environments = contract?.environments;
|
|
273
325
|
if (
|
|
274
326
|
environments == null ||
|
|
@@ -320,7 +372,7 @@ export function resolveQaEnvironment(contract, target) {
|
|
|
320
372
|
return {
|
|
321
373
|
name: resolvedName,
|
|
322
374
|
baseUrl: env.baseUrl,
|
|
323
|
-
signInSeam: env.signInSeam,
|
|
375
|
+
signInSeam: resolveSignInSeam(env.signInSeam, resolvedName, options),
|
|
324
376
|
allowWrites,
|
|
325
377
|
};
|
|
326
378
|
}
|
|
@@ -91,7 +91,7 @@ export const EVENT_KINDS = Object.freeze({
|
|
|
91
91
|
// Story #3819 — per-criterion acceptance self-eval signal emitted by
|
|
92
92
|
// acceptance-eval.js. One record per Story per eval-loop terminus,
|
|
93
93
|
// carrying which acceptance items needed rework and the round count, so
|
|
94
|
-
// the retro and /plan Phase 0 feedback fetch can see acceptance
|
|
94
|
+
// the retro and /mandrel-plan Phase 0 feedback fetch can see acceptance
|
|
95
95
|
// churn alongside friction/hotspot data.
|
|
96
96
|
ACCEPTANCE_EVAL: 'acceptance-eval',
|
|
97
97
|
// Forensic breadcrumb appended to the per-Epic stream by the notify
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// .agents/scripts/lib/skills/skills-index.js
|
|
2
|
+
//
|
|
3
|
+
// Shared I/O for the two skills manifests (Story #5135).
|
|
4
|
+
//
|
|
5
|
+
// Each skills root carries its own `skills.index.json`: the package payload's
|
|
6
|
+
// at `.agents/skills/`, and the consumer-writable zone's at
|
|
7
|
+
// `.agents/local/skills/`. The shipped one is a committed payload file that
|
|
8
|
+
// `mandrel doctor` / `mandrel sync-agents` compare byte-for-byte against the
|
|
9
|
+
// installed package, so the two manifests must never be merged — but they are
|
|
10
|
+
// read, compared and reported identically, and both CLIs need that logic.
|
|
11
|
+
// Before this module `generate-skills-index.js` and `validate-skills.js`
|
|
12
|
+
// each carried their own near-identical reader.
|
|
13
|
+
|
|
14
|
+
import fs from 'node:fs';
|
|
15
|
+
import path from 'node:path';
|
|
16
|
+
|
|
17
|
+
/** Manifest filename, shared by both roots. */
|
|
18
|
+
export const INDEX_FILENAME = 'skills.index.json';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Absolute path of the manifest for one skills root.
|
|
22
|
+
*
|
|
23
|
+
* @param {string} repoRoot
|
|
24
|
+
* @param {readonly string[]} rootSegments From `walk-skill-files.js`.
|
|
25
|
+
* @returns {string}
|
|
26
|
+
*/
|
|
27
|
+
export function indexPathFor(repoRoot, rootSegments) {
|
|
28
|
+
return path.join(repoRoot, ...rootSegments, INDEX_FILENAME);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Read a manifest from disk. Distinguishes "missing" from "unparseable" via
|
|
33
|
+
* the `reason` channel so callers can report which drift they hit rather than
|
|
34
|
+
* collapsing both into "not fresh".
|
|
35
|
+
*
|
|
36
|
+
* @param {string} indexPath
|
|
37
|
+
* @returns {{ manifest: object | null, reason: string | null }}
|
|
38
|
+
*/
|
|
39
|
+
export function readManifest(indexPath) {
|
|
40
|
+
if (!fs.existsSync(indexPath)) {
|
|
41
|
+
return { manifest: null, reason: 'missing' };
|
|
42
|
+
}
|
|
43
|
+
let src;
|
|
44
|
+
try {
|
|
45
|
+
src = fs.readFileSync(indexPath, 'utf8');
|
|
46
|
+
} catch (err) {
|
|
47
|
+
return { manifest: null, reason: `read-error: ${err.message}` };
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
return { manifest: JSON.parse(src), reason: null };
|
|
51
|
+
} catch (err) {
|
|
52
|
+
return { manifest: null, reason: `parse-error: ${err.message}` };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Read a manifest and project its entry paths into a Set, the shape the
|
|
58
|
+
* validator's membership check consumes.
|
|
59
|
+
*
|
|
60
|
+
* @param {string} indexPath
|
|
61
|
+
* @returns {{ exists: boolean, paths: Set<string> | null, manifest: object | null, indexPath: string, parseError?: string }}
|
|
62
|
+
*/
|
|
63
|
+
export function readIndexPaths(indexPath) {
|
|
64
|
+
const { manifest, reason } = readManifest(indexPath);
|
|
65
|
+
if (reason === 'missing') {
|
|
66
|
+
return { exists: false, paths: null, manifest: null, indexPath };
|
|
67
|
+
}
|
|
68
|
+
if (manifest === null) {
|
|
69
|
+
return {
|
|
70
|
+
exists: true,
|
|
71
|
+
paths: null,
|
|
72
|
+
manifest: null,
|
|
73
|
+
indexPath,
|
|
74
|
+
parseError: reason,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
const paths = new Set(
|
|
78
|
+
Array.isArray(manifest.skills)
|
|
79
|
+
? manifest.skills.map((s) => s.path).filter((p) => typeof p === 'string')
|
|
80
|
+
: [],
|
|
81
|
+
);
|
|
82
|
+
return { exists: true, paths, manifest, indexPath };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Compare two manifests ignoring `generatedAt` — the one volatile field, which
|
|
87
|
+
* changes on every write and is not content. Returns null when they match, or
|
|
88
|
+
* a diff-style message naming the entry counts.
|
|
89
|
+
*
|
|
90
|
+
* @param {object | null} diskManifest
|
|
91
|
+
* @param {object} freshManifest
|
|
92
|
+
* @param {string} label Manifest name for the message.
|
|
93
|
+
* @returns {string | null}
|
|
94
|
+
*/
|
|
95
|
+
export function diffManifests(diskManifest, freshManifest, label) {
|
|
96
|
+
if (diskManifest === null) {
|
|
97
|
+
return `${label}: on-disk manifest is missing or unreadable`;
|
|
98
|
+
}
|
|
99
|
+
const a = { ...diskManifest };
|
|
100
|
+
const b = { ...freshManifest };
|
|
101
|
+
a.generatedAt = undefined;
|
|
102
|
+
b.generatedAt = undefined;
|
|
103
|
+
if (JSON.stringify(a) === JSON.stringify(b)) return null;
|
|
104
|
+
const count = (m) => (Array.isArray(m.skills) ? m.skills.length : 'n/a');
|
|
105
|
+
return [
|
|
106
|
+
`${label} drift detected:`,
|
|
107
|
+
` on-disk entries: ${count(diskManifest)}`,
|
|
108
|
+
` generated entries: ${count(freshManifest)}`,
|
|
109
|
+
" run 'node .agents/scripts/generate-skills-index.js' to refresh",
|
|
110
|
+
].join('\n');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Render a manifest's schema violations as field-named findings. The compiled
|
|
115
|
+
* AJV validator is passed in so this module stays free of the schema-loading
|
|
116
|
+
* side effects the validator CLI owns.
|
|
117
|
+
*
|
|
118
|
+
* @param {object} manifest
|
|
119
|
+
* @param {string} indexRelPath Repo-relative manifest path, for the message.
|
|
120
|
+
* @param {(m: object) => boolean} validateManifest Compiled AJV validator.
|
|
121
|
+
* @returns {string[]}
|
|
122
|
+
*/
|
|
123
|
+
function validateManifestSchema(manifest, indexRelPath, validateManifest) {
|
|
124
|
+
const findings = [];
|
|
125
|
+
if (validateManifest(manifest)) return findings;
|
|
126
|
+
for (const err of validateManifest.errors ?? []) {
|
|
127
|
+
const where = err.instancePath || '(root)';
|
|
128
|
+
findings.push(
|
|
129
|
+
`${indexRelPath}: manifest-schema: schema violation at ${where}: ${err.message}`,
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
return findings;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Audit one root's manifest: present, parseable, and schema-valid. Shared by
|
|
137
|
+
* both skills roots so a consumer-authored index is held to the same bar as
|
|
138
|
+
* the shipped one.
|
|
139
|
+
*
|
|
140
|
+
* @param {{ exists: boolean, paths: Set<string> | null, manifest: object | null, parseError?: string }} indexInfo
|
|
141
|
+
* @param {string} indexRelPath
|
|
142
|
+
* @param {(m: object) => boolean} validateManifest
|
|
143
|
+
* @param {{ required: boolean }} options
|
|
144
|
+
* @returns {string[]}
|
|
145
|
+
*/
|
|
146
|
+
export function auditIndex(
|
|
147
|
+
indexInfo,
|
|
148
|
+
indexRelPath,
|
|
149
|
+
validateManifest,
|
|
150
|
+
{ required },
|
|
151
|
+
) {
|
|
152
|
+
if (!indexInfo.exists) {
|
|
153
|
+
return required
|
|
154
|
+
? [
|
|
155
|
+
`index missing: ${indexRelPath} not found — run 'node .agents/scripts/generate-skills-index.js'`,
|
|
156
|
+
]
|
|
157
|
+
: [];
|
|
158
|
+
}
|
|
159
|
+
if (indexInfo.paths === null) {
|
|
160
|
+
return [`index unparseable: ${indexRelPath} — ${indexInfo.parseError}`];
|
|
161
|
+
}
|
|
162
|
+
if (indexInfo.manifest === null) return [];
|
|
163
|
+
return validateManifestSchema(
|
|
164
|
+
indexInfo.manifest,
|
|
165
|
+
indexRelPath,
|
|
166
|
+
validateManifest,
|
|
167
|
+
);
|
|
168
|
+
}
|
|
@@ -1,12 +1,56 @@
|
|
|
1
1
|
// .agents/scripts/lib/skills/walk-skill-files.js
|
|
2
2
|
//
|
|
3
|
-
// Shared traversal for SKILL.md files
|
|
3
|
+
// Shared traversal for SKILL.md files across the two skills roots:
|
|
4
|
+
// the package payload (`.agents/skills/{core,stack}/`) and the
|
|
5
|
+
// consumer-writable local zone (`.agents/local/skills/{core,stack}/`).
|
|
4
6
|
// Used by validate-skills.js and generate-skills-index.js so both CLIs
|
|
5
7
|
// enumerate the same paths in the same deterministic order.
|
|
8
|
+
//
|
|
9
|
+
// The two roots stay **separately enumerable** on purpose (Story #5135).
|
|
10
|
+
// `.agents/skills/skills.index.json` is a committed payload file that
|
|
11
|
+
// `mandrel doctor` / `mandrel sync-agents` compare byte-for-byte against
|
|
12
|
+
// the installed package; folding a consumer's local skills into it would
|
|
13
|
+
// make every consumer's regenerated index read as payload drift and cause
|
|
14
|
+
// those commands to refuse. The local zone therefore carries its own
|
|
15
|
+
// index artifact, and the two roots are unified only at *lookup* time, by
|
|
16
|
+
// `resolveSkillFile` — never for the shipped manifest.
|
|
6
17
|
|
|
7
18
|
import fs from 'node:fs';
|
|
8
19
|
import path from 'node:path';
|
|
9
20
|
|
|
21
|
+
/** Tier directories a skills root is enumerated under. */
|
|
22
|
+
const TIERS = Object.freeze(['core', 'stack']);
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Path segments (from the repo root) of the package-payload skills root.
|
|
26
|
+
* Materialized by `mandrel sync`; every file under it is payload.
|
|
27
|
+
*/
|
|
28
|
+
export const PAYLOAD_SKILLS_SEGMENTS = Object.freeze(['.agents', 'skills']);
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Path segments (from the repo root) of the consumer-writable skills root.
|
|
32
|
+
* It sits inside the `.agents/local/` zone (Story #3498), which sync never
|
|
33
|
+
* copies into and never prunes, and which the agents-drift check cannot
|
|
34
|
+
* flag because that check only walks files present in the package payload.
|
|
35
|
+
*/
|
|
36
|
+
export const LOCAL_SKILLS_SEGMENTS = Object.freeze([
|
|
37
|
+
'.agents',
|
|
38
|
+
'local',
|
|
39
|
+
'skills',
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* A skill id is the tier-relative path naming a skill — e.g.
|
|
44
|
+
* `core/scope-triage` or `stack/qa/playwright`. It is the value that
|
|
45
|
+
* appears in `skills.index.json` minus the root prefix, and the value a
|
|
46
|
+
* `qa.environments.*.signInSeam.skill` seam carries.
|
|
47
|
+
*
|
|
48
|
+
* The pattern is deliberately strict: ids resolve to filesystem paths, so
|
|
49
|
+
* anything that could escape a root (`..`, absolute paths, backslashes) or
|
|
50
|
+
* smuggle a shell metacharacter is rejected rather than normalized.
|
|
51
|
+
*/
|
|
52
|
+
const SKILL_ID_RE = /^[a-z0-9][a-z0-9._-]*(?:\/[a-z0-9][a-z0-9._-]*)+$/;
|
|
53
|
+
|
|
10
54
|
/**
|
|
11
55
|
* Recursively enumerate `SKILL.md` paths under a directory.
|
|
12
56
|
*
|
|
@@ -38,19 +82,99 @@ function walkSkillFiles(rootDir) {
|
|
|
38
82
|
}
|
|
39
83
|
|
|
40
84
|
/**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
85
|
+
* Sort absolute paths by their POSIX repo-relative form so output order is
|
|
86
|
+
* deterministic across platforms.
|
|
43
87
|
*
|
|
88
|
+
* @param {string[]} files
|
|
44
89
|
* @param {string} repoRoot
|
|
45
|
-
* @returns {string[]}
|
|
90
|
+
* @returns {string[]}
|
|
46
91
|
*/
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const coreFiles = walkSkillFiles(path.join(skillsRoot, 'core'));
|
|
50
|
-
const stackFiles = walkSkillFiles(path.join(skillsRoot, 'stack'));
|
|
51
|
-
return [...coreFiles, ...stackFiles].sort((a, b) => {
|
|
92
|
+
function sortByRepoRelative(files, repoRoot) {
|
|
93
|
+
return [...files].sort((a, b) => {
|
|
52
94
|
const ra = path.relative(repoRoot, a).split(path.sep).join('/');
|
|
53
95
|
const rb = path.relative(repoRoot, b).split(path.sep).join('/');
|
|
54
96
|
return ra < rb ? -1 : ra > rb ? 1 : 0;
|
|
55
97
|
});
|
|
56
98
|
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Enumerate the `SKILL.md` files under one skills root, sorted by POSIX
|
|
102
|
+
* repo-relative path.
|
|
103
|
+
*
|
|
104
|
+
* @param {string} repoRoot
|
|
105
|
+
* @param {readonly string[]} rootSegments One of the exported segment lists.
|
|
106
|
+
* @returns {string[]} absolute paths
|
|
107
|
+
*/
|
|
108
|
+
function collectUnderRoot(repoRoot, rootSegments) {
|
|
109
|
+
const skillsRoot = path.join(repoRoot, ...rootSegments);
|
|
110
|
+
const files = TIERS.flatMap((tier) =>
|
|
111
|
+
walkSkillFiles(path.join(skillsRoot, tier)),
|
|
112
|
+
);
|
|
113
|
+
return sortByRepoRelative(files, repoRoot);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Build the list of payload SKILL.md files under
|
|
118
|
+
* `<repoRoot>/.agents/skills/{core,stack}/`.
|
|
119
|
+
*
|
|
120
|
+
* This is the set the **shipped** `skills.index.json` is generated from —
|
|
121
|
+
* it must never include local-zone skills (see the module header).
|
|
122
|
+
*
|
|
123
|
+
* @param {string} repoRoot
|
|
124
|
+
* @returns {string[]} absolute paths
|
|
125
|
+
*/
|
|
126
|
+
export function collectSkillFiles(repoRoot) {
|
|
127
|
+
return collectUnderRoot(repoRoot, PAYLOAD_SKILLS_SEGMENTS);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Build the list of consumer-authored SKILL.md files under
|
|
132
|
+
* `<repoRoot>/.agents/local/skills/{core,stack}/`. Empty when the
|
|
133
|
+
* consumer has authored none — the common case, and the case in this
|
|
134
|
+
* repository itself.
|
|
135
|
+
*
|
|
136
|
+
* @param {string} repoRoot
|
|
137
|
+
* @returns {string[]} absolute paths
|
|
138
|
+
*/
|
|
139
|
+
export function collectLocalSkillFiles(repoRoot) {
|
|
140
|
+
return collectUnderRoot(repoRoot, LOCAL_SKILLS_SEGMENTS);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Resolve a skill id to a readable `SKILL.md`, searching the payload root
|
|
145
|
+
* first and the local zone second (payload-wins, matching
|
|
146
|
+
* {@link collectAllSkillFiles}).
|
|
147
|
+
*
|
|
148
|
+
* Returns `null` rather than throwing so callers own the error message —
|
|
149
|
+
* a config resolver wants to name the offending config key, a workflow
|
|
150
|
+
* wants to name the seam.
|
|
151
|
+
*
|
|
152
|
+
* @param {string} repoRoot
|
|
153
|
+
* @param {string} skillId Tier-relative id, e.g. `stack/qa/acme-sso`.
|
|
154
|
+
* @returns {{ path: string, root: string } | null} absolute `SKILL.md`
|
|
155
|
+
* path and the POSIX repo-relative root it resolved under.
|
|
156
|
+
*/
|
|
157
|
+
export function resolveSkillFile(repoRoot, skillId) {
|
|
158
|
+
if (typeof skillId !== 'string' || !SKILL_ID_RE.test(skillId)) return null;
|
|
159
|
+
for (const segments of [PAYLOAD_SKILLS_SEGMENTS, LOCAL_SKILLS_SEGMENTS]) {
|
|
160
|
+
const candidate = path.join(repoRoot, ...segments, skillId, 'SKILL.md');
|
|
161
|
+
try {
|
|
162
|
+
if (fs.statSync(candidate).isFile()) {
|
|
163
|
+
return { path: candidate, root: segments.join('/') };
|
|
164
|
+
}
|
|
165
|
+
} catch {
|
|
166
|
+
// Unreadable or absent — try the next root.
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* The POSIX repo-relative skills roots, in search order. Exported so error
|
|
174
|
+
* messages can name exactly what was searched rather than restating the
|
|
175
|
+
* paths as literals.
|
|
176
|
+
*/
|
|
177
|
+
export const SKILL_SEARCH_ROOTS = Object.freeze([
|
|
178
|
+
PAYLOAD_SKILLS_SEGMENTS.join('/'),
|
|
179
|
+
LOCAL_SKILLS_SEGMENTS.join('/'),
|
|
180
|
+
]);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* lib/story-adjacency.js — the single story-level adjacency builder.
|
|
3
3
|
*
|
|
4
|
-
* Both pre-v2 Epic wave wrappers and the v2 `/deliver` ready-set path bottom out in the shared
|
|
4
|
+
* Both pre-v2 Epic wave wrappers and the v2 `/mandrel-deliver` ready-set path bottom out in the shared
|
|
5
5
|
* `lib/Graph.js` kernel (`detectCycle` / `assignLayers` / `computeWaves`),
|
|
6
6
|
* but each historically re-implemented the step that turns a list of Story
|
|
7
7
|
* records into the `Map<storyId, number[]>` adjacency the kernel consumes.
|
|
@@ -43,7 +43,7 @@ import { parseBlockedBy } from './dependency-parser.js';
|
|
|
43
43
|
* DAG nodes).
|
|
44
44
|
* @param {object} [opts]
|
|
45
45
|
* @param {boolean} [opts.dropForeign=false] When `false` (the v2 default,
|
|
46
|
-
* matching the `/deliver` path — `stories-wave-tick.js` and the
|
|
46
|
+
* matching the `/mandrel-deliver` path — `stories-wave-tick.js` and the
|
|
47
47
|
* `planReadySet` core), the operator-DAG contract is preserved: a
|
|
48
48
|
* dependency on an id absent from the input is treated as not-yet-done
|
|
49
49
|
* and withholds the dependent until it completes. When `true` (the
|
|
@@ -21,7 +21,7 @@ import { BODY_FORMAT_LINTS } from '../story-body/body-format-lints.js';
|
|
|
21
21
|
*
|
|
22
22
|
* **Single source of the prompt body (Story #4162).** This module is the sole
|
|
23
23
|
* carrier of the full decomposer system-prompt body, delivered to the host
|
|
24
|
-
* LLM in the `systemPrompts.decompose` field of the `/plan` context envelope
|
|
24
|
+
* LLM in the `systemPrompts.decompose` field of the `/mandrel-plan` context envelope
|
|
25
25
|
* (via `lib/orchestration/planning/decomposer-context.js`), so no second
|
|
26
26
|
* verbatim copy can drift.
|
|
27
27
|
*/
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* Section-scoped writes are the load-bearing contract (extending the
|
|
13
13
|
* single-writer discipline #4303 established for the body trailer):
|
|
14
14
|
*
|
|
15
|
-
* - The `/plan` persist path upserts the `techSpec` and `acceptanceTable`
|
|
15
|
+
* - The `/mandrel-plan` persist path upserts the `techSpec` and `acceptanceTable`
|
|
16
16
|
* regions.
|
|
17
17
|
* - The close-time acceptance reconciler
|
|
18
18
|
* (`acceptance-spec-reconciler.js`) reads and rewrites the
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
/**
|
|
17
17
|
* The one bound every independent-write fan-out over the GitHub API uses
|
|
18
18
|
* (Story #4952 raised those loops off serial; Story #4961 made this the single
|
|
19
|
-
* owner of the number they share). Imported by the `/plan` context gathers,
|
|
19
|
+
* owner of the number they share). Imported by the `/mandrel-plan` context gathers,
|
|
20
20
|
* the persist checkpoint fan-out, the `agent::ready` flips and the supersede
|
|
21
21
|
* close loop, so re-tuning the policy is one edit rather than six.
|
|
22
22
|
*
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* parse-id-list — expand a Story-id list that may contain dash ranges.
|
|
3
3
|
*
|
|
4
4
|
* Operators name a contiguous span of Stories the way they read one — as a
|
|
5
|
-
* range: `/deliver 4922 - 4926`. Enumerating it by hand is the kind of
|
|
5
|
+
* range: `/mandrel-deliver 4922 - 4926`. Enumerating it by hand is the kind of
|
|
6
6
|
* transcription step that silently drops or invents an id, so the range is a
|
|
7
7
|
* first-class shape of every delivery id list rather than something the host
|
|
8
8
|
* expands from prose.
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* in-flight count, and it decides. Until now the only adapter was the
|
|
8
8
|
* flag-driven one (`stories-wave-tick.js --dag/--done/--in-flight`), which
|
|
9
9
|
* pushed the *gathering* of those inputs onto the caller — in practice onto
|
|
10
|
-
* the host LLM following `/deliver`'s prose, re-seeding `--done` and counting
|
|
10
|
+
* the host LLM following `/mandrel-deliver`'s prose, re-seeding `--done` and counting
|
|
11
11
|
* `--in-flight` by hand every beat. That is hand-maintained accounting on the
|
|
12
12
|
* one correctness-critical path where a mistake silently wedges a run (a
|
|
13
13
|
* dropped foreign blocker) or double-dispatches a Story (a miscounted slot).
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
* The graph resolution is **not** reimplemented here — it reuses
|
|
48
48
|
* `resolve-stories.js`'s machinery wholesale (body `depends_on` ∪ native
|
|
49
49
|
* `blocked_by` edges, foreign-blocker resolution, `files[]` footprints), so
|
|
50
|
-
* the probe and `/deliver`'s step-1 resolution cannot disagree about what
|
|
50
|
+
* the probe and `/mandrel-deliver`'s step-1 resolution cannot disagree about what
|
|
51
51
|
* depends on what.
|
|
52
52
|
*
|
|
53
53
|
* @module lib/wave-runner/live-probe
|
|
@@ -127,7 +127,7 @@ function deriveInFlightIds(storyRecords, dispatched = []) {
|
|
|
127
127
|
* in-flight, so `detectWedge` dropped it (its "undone work with no unmet
|
|
128
128
|
* blockers would have been dispatched" invariant is precisely what probe mode
|
|
129
129
|
* broke) and the beat reported exit 0 / `ready: []` / `wedged: null` forever.
|
|
130
|
-
* `/deliver` reads that as "waiting", so the `agent::blocked` HITL pause — the
|
|
130
|
+
* `/mandrel-deliver` reads that as "waiting", so the `agent::blocked` HITL pause — the
|
|
131
131
|
* one runtime gate in the protocol — was never surfaced to the operator.
|
|
132
132
|
*
|
|
133
133
|
* @param {Array<{id?: number, number?: number, labels?: string[], state?: string}>} storyRecords
|
|
@@ -193,7 +193,7 @@ function deriveForeignHeld(storyRecords, self, warn) {
|
|
|
193
193
|
* Resolve the provider + repo coordinates the probe reads through.
|
|
194
194
|
*
|
|
195
195
|
* Shares `resolve-stories.js`'s provider seam, so probe mode authenticates and
|
|
196
|
-
* targets exactly the same repo `/deliver`'s resolution step does. Tests
|
|
196
|
+
* targets exactly the same repo `/mandrel-deliver`'s resolution step does. Tests
|
|
197
197
|
* inject a stub provider instead of calling this.
|
|
198
198
|
*
|
|
199
199
|
* @param {object} [deps]
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* lib/wave-runner/ready-set.js — the path-agnostic ready-set scheduling
|
|
3
3
|
* core.
|
|
4
4
|
*
|
|
5
|
-
* This module is the scheduling kernel the v2 `/deliver` multi-Story path
|
|
5
|
+
* This module is the scheduling kernel the v2 `/mandrel-deliver` multi-Story path
|
|
6
6
|
* dispatches through (`stories-wave-tick.js`). It replaces wave-*batch* selection
|
|
7
7
|
* (group N must fully drain before group N+1 opens) with *continuous*,
|
|
8
8
|
* dependency-driven selection: a Story becomes dispatchable the instant
|
|
@@ -345,7 +345,7 @@ function loadDocs(root, fs) {
|
|
|
345
345
|
* (`helpers/deliver-story.md` → `# /deliver-story …`). The self-naming test is
|
|
346
346
|
* what separates an invocable helper from an appendix that merely titles itself
|
|
347
347
|
* after the command it documents (`helpers/deliver-reference.md` →
|
|
348
|
-
* `# /deliver — reference appendix`), which is read on demand, never invoked.
|
|
348
|
+
* `# /mandrel-deliver — reference appendix`), which is read on demand, never invoked.
|
|
349
349
|
*
|
|
350
350
|
* @param {WorkflowDoc} doc
|
|
351
351
|
* @returns {boolean}
|
|
@@ -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
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* git-ignored; no tracked state is mutated.
|
|
18
18
|
*
|
|
19
19
|
* **Story #5112 — the manifest is shared mutable state.** `recordPendingCleanup`
|
|
20
|
-
* runs from the close reap and `drainPendingCleanup` runs from the `/plan`
|
|
20
|
+
* runs from the close reap and `drainPendingCleanup` runs from the `/mandrel-plan`
|
|
21
21
|
* boot, so two concurrent delivery sessions can read-modify-write the same
|
|
22
22
|
* file. Three properties close that:
|
|
23
23
|
*
|
|
@@ -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
|