mandrel 2.30.0 → 2.32.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 +13 -17
- package/.agents/agents/acceptance-critic.md +1 -2
- package/.agents/docs/SDLC.md +4 -4
- package/.agents/docs/agentrc-reference.json +61 -57
- package/.agents/docs/configuration.md +274 -227
- package/.agents/docs/execution-reference.md +13 -14
- package/.agents/docs/quality-gates.md +195 -23
- package/.agents/instructions.md +2 -5
- package/.agents/rules/ci-remediation.md +44 -1
- package/.agents/rules/git-conventions-reference.md +27 -27
- package/.agents/rules/git-conventions.md +4 -2
- package/.agents/rules/known-tooling-behavior.md +66 -30
- package/.agents/rules/testing-standards.md +35 -71
- package/.agents/runtime-deps.json +0 -1
- package/.agents/schemas/agentrc.schema.json +1939 -1400
- package/.agents/schemas/lifecycle/README.md +21 -14
- package/.agents/schemas/lifecycle/ledger-record.schema.json +76 -22
- package/.agents/schemas/story-deliver-terminal.schema.json +2 -2
- package/.agents/scripts/README.md +7 -29
- package/.agents/scripts/apply-quality-bootstrap.js +27 -34
- package/.agents/scripts/bootstrap.js +28 -26
- package/.agents/scripts/check-baseline-drift.js +73 -13
- package/.agents/scripts/check-baseline-scope.js +362 -0
- package/.agents/scripts/check-dead-exports.js +9 -1
- package/.agents/scripts/check-gherkin-corpus.js +508 -0
- package/.agents/scripts/check-knip-entries.js +136 -0
- package/.agents/scripts/check-lifecycle-lint.js +36 -112
- package/.agents/scripts/check-schema-references.js +1 -1
- package/.agents/scripts/diagnose-friction.js +7 -4
- package/.agents/scripts/generate-config-docs.js +263 -171
- package/.agents/scripts/install-matrix-assert.js +0 -1
- package/.agents/scripts/lib/ITicketingProvider.js +0 -58
- package/.agents/scripts/lib/audit-baselines/staleness.js +6 -6
- package/.agents/scripts/lib/audit-baselines/trend.js +7 -8
- package/.agents/scripts/lib/audit-baselines/weights.js +4 -5
- package/.agents/scripts/lib/audit-suite/checklist-threading.js +1 -1
- package/.agents/scripts/lib/audit-to-stories/build-story-body.js +0 -1
- package/.agents/scripts/lib/baselines/envelope.js +41 -60
- package/.agents/scripts/lib/baselines/git-base.js +30 -37
- package/.agents/scripts/lib/baselines/kinds/_crap-new-method-gate.js +103 -0
- package/.agents/scripts/lib/baselines/kinds/_crap-read.js +150 -0
- package/.agents/scripts/lib/baselines/kinds/crap.js +25 -65
- package/.agents/scripts/lib/baselines/orphan-pruner.js +233 -0
- package/.agents/scripts/lib/baselines/refresh-service.js +6 -8
- package/.agents/scripts/lib/baselines/scope-assert.js +223 -0
- package/.agents/scripts/lib/baselines/scope-inventory.js +314 -0
- package/.agents/scripts/lib/bdd-step-index.js +326 -0
- package/.agents/scripts/lib/bootstrap/install-ledger.js +5 -3
- package/.agents/scripts/lib/bootstrap/issue-forms-template.js +4 -6
- package/.agents/scripts/lib/bootstrap/manifest.js +17 -40
- package/.agents/scripts/lib/bootstrap/project-bootstrap.js +12 -59
- package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +62 -2
- package/.agents/scripts/lib/checks/loop-health.js +9 -37
- package/.agents/scripts/lib/child-exec.js +193 -0
- package/.agents/scripts/lib/cli/standard-args.js +1 -1
- package/.agents/scripts/lib/cli-args.js +64 -0
- package/.agents/scripts/lib/close-validation/gates.js +2 -2
- package/.agents/scripts/lib/close-validation/runner.js +3 -3
- package/.agents/scripts/lib/config/acceptance-eval.js +5 -52
- package/.agents/scripts/lib/config/commands.js +3 -5
- package/.agents/scripts/lib/config/explain.js +5 -7
- package/.agents/scripts/lib/config/gates/bundle-size.schema.js +32 -6
- package/.agents/scripts/lib/config/gates/coverage.schema.js +25 -5
- package/.agents/scripts/lib/config/gates/crap-incremental-coverage.schema.js +12 -2
- package/.agents/scripts/lib/config/gates/crap.schema.js +68 -23
- package/.agents/scripts/lib/config/gates/duplication.schema.js +29 -17
- package/.agents/scripts/lib/config/gates/index.js +5 -2
- package/.agents/scripts/lib/config/gates/lighthouse.schema.js +34 -6
- package/.agents/scripts/lib/config/gates/lint.schema.js +11 -2
- package/.agents/scripts/lib/config/gates/maintainability.schema.js +37 -15
- package/.agents/scripts/lib/config/gates/mutation.schema.js +15 -3
- package/.agents/scripts/lib/config/gates/shared.js +58 -9
- package/.agents/scripts/lib/config/github.js +0 -1
- package/.agents/scripts/lib/config/limits.js +3 -48
- package/.agents/scripts/lib/config/qa.js +105 -0
- package/.agents/scripts/lib/config/temp-paths.js +6 -5
- package/.agents/scripts/lib/config-settings-schema-delivery.js +237 -56
- package/.agents/scripts/lib/config-settings-schema-quality.js +209 -29
- package/.agents/scripts/lib/config-settings-schema.js +386 -39
- package/.agents/scripts/lib/crap-baseline-join.js +126 -9
- package/.agents/scripts/lib/crap-utils.js +84 -520
- package/.agents/scripts/lib/dead-exports-knip.js +79 -10
- package/.agents/scripts/lib/degraded-mode.js +2 -2
- package/.agents/scripts/lib/doc-tiers.js +3 -3
- package/.agents/scripts/lib/feedback-loop/graduator-core.js +46 -104
- package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +10 -8
- package/.agents/scripts/lib/fs-walk.js +52 -0
- package/.agents/scripts/lib/git-branch-lifecycle.js +2 -2
- package/.agents/scripts/lib/git-utils.js +16 -36
- package/.agents/scripts/lib/knip-entry-sync.js +469 -0
- package/.agents/scripts/lib/observability/metrics-ledger.js +1 -1
- package/.agents/scripts/lib/observability/runtime-friction.js +10 -0
- package/.agents/scripts/lib/observability/signal-validator.js +5 -85
- package/.agents/scripts/lib/observability/signals-writer.js +19 -62
- package/.agents/scripts/lib/observability/source-classifier.js +5 -7
- package/.agents/scripts/lib/observability/terse-result.js +3 -3
- package/.agents/scripts/lib/orchestration/behind-recovery.js +114 -0
- package/.agents/scripts/lib/orchestration/ceremony-routing.js +7 -8
- package/.agents/scripts/lib/orchestration/ci-rerun-guard.js +34 -33
- package/.agents/scripts/lib/orchestration/code-review.js +2 -2
- package/.agents/scripts/lib/orchestration/complexity-gate.js +43 -161
- package/.agents/scripts/lib/orchestration/diff-magnitude.js +4 -4
- package/.agents/scripts/lib/orchestration/label-transitions.js +3 -2
- package/.agents/scripts/lib/orchestration/lease-guard-shared.js +12 -38
- package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +5 -6
- package/.agents/scripts/lib/orchestration/plan-metrics.js +2 -3
- package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +6 -0
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +0 -1
- package/.agents/scripts/lib/orchestration/{lifecycle/listeners/watcher.js → pr-watch.js} +58 -208
- package/.agents/scripts/lib/orchestration/resolve-stories.js +5 -15
- package/.agents/scripts/lib/orchestration/review-providers/codex.js +1 -1
- package/.agents/scripts/lib/orchestration/review-providers/mi-exemptions.js +130 -0
- package/.agents/scripts/lib/orchestration/review-providers/native.js +30 -16
- package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +1 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +37 -26
- package/.agents/scripts/lib/orchestration/single-story-close/phases/conventional-subject.js +376 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/normalize-pr-title.js +161 -151
- package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +15 -3
- package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +10 -15
- package/.agents/scripts/lib/orchestration/single-story-close/phases/push.js +17 -2
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +5 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-override.js +157 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +0 -14
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +65 -25
- package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +20 -31
- package/.agents/scripts/lib/orchestration/spec-spill.js +17 -3
- package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +7 -6
- package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +2 -1
- package/.agents/scripts/lib/orchestration/task-body-validator.js +4 -1
- package/.agents/scripts/lib/orchestration/ticket-lease.js +28 -127
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +1 -1
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +5 -5
- package/.agents/scripts/lib/orchestration/ticketing/transition.js +5 -4
- package/.agents/scripts/lib/orchestration/verify-tier-repair.js +107 -0
- package/.agents/scripts/lib/qa/coverage-verdict.js +5 -87
- package/.agents/scripts/lib/signals/detectors/common.js +1 -1
- package/.agents/scripts/lib/signals/index.js +8 -6
- package/.agents/scripts/lib/signals/schema.js +20 -25
- package/.agents/scripts/lib/signals/write.js +8 -8
- package/.agents/scripts/lib/story-body/story-body.js +12 -59
- package/.agents/scripts/lib/temp-retention.js +1 -1
- package/.agents/scripts/lib/templates/decomposer-prompts.js +16 -14
- package/.agents/scripts/lib/ticket-body-sections.js +4 -5
- package/.agents/scripts/lib/worktree/lifecycle/merge-reachability.js +13 -45
- package/.agents/scripts/lib/worktree/lifecycle/reap.js +4 -5
- package/.agents/scripts/lib/worktree-manager.js +2 -3
- package/.agents/scripts/lint-label-vocabulary.js +2 -24
- package/.agents/scripts/pr-watch-with-update.js +7 -5
- package/.agents/scripts/providers/github/cache.js +2 -2
- package/.agents/scripts/providers/github/comments.js +6 -28
- package/.agents/scripts/providers/github/compose.js +0 -15
- package/.agents/scripts/providers/github/errors.js +10 -27
- package/.agents/scripts/providers/github/request-helpers.js +1 -2
- package/.agents/scripts/providers/github/sub-issues.js +10 -218
- package/.agents/scripts/providers/github.js +4 -7
- package/.agents/scripts/prune-baseline-orphans.js +181 -0
- package/.agents/scripts/resolve-stories.js +0 -2
- package/.agents/scripts/run-lint.js +61 -61
- package/.agents/scripts/run-test-profile.js +6 -6
- package/.agents/scripts/run-verify.js +48 -30
- package/.agents/scripts/single-story-close.js +20 -0
- package/.agents/scripts/single-story-init.js +12 -35
- package/.agents/scripts/update-dead-exports-baseline.js +321 -0
- package/.agents/skills/core/gates-and-baselines/SKILL.md +2 -2
- package/.agents/skills/skills.index.json +2 -12
- package/.agents/skills/stack/qa/playwright/SKILL.md +48 -0
- package/.agents/workflows/audit-documentation.md +5 -6
- package/.agents/workflows/audit-to-stories.md +2 -2
- package/.agents/workflows/helpers/audit-lens-core.md +11 -12
- package/.agents/workflows/helpers/code-quality-guardrails.md +15 -14
- package/.agents/workflows/helpers/code-review.md +3 -8
- package/.agents/workflows/helpers/deliver-reference.md +2 -1
- package/.agents/workflows/helpers/deliver-story-reference.md +27 -16
- package/.agents/workflows/helpers/worktree-lifecycle.md +1 -2
- package/.agents/workflows/mandrel-update.md +10 -10
- package/.agents/workflows/qa-assist.md +15 -20
- package/.agents/workflows/qa-explore.md +9 -8
- package/README.md +1 -1
- package/docs/CHANGELOG.md +49 -0
- package/lib/migrations/index.js +2 -0
- package/lib/migrations/steps/2.32.0-retire-lint-baseline-command.js +127 -0
- package/package.json +12 -3
- package/.agents/schemas/lifecycle/checkpoint.written.schema.json +0 -13
- package/.agents/schemas/lifecycle/close-validate.end.schema.json +0 -18
- package/.agents/schemas/lifecycle/close-validate.start.schema.json +0 -13
- package/.agents/schemas/lifecycle/code-review.end.schema.json +0 -30
- package/.agents/schemas/lifecycle/code-review.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/intervention.recorded.schema.json +0 -15
- package/.agents/schemas/lifecycle/loop.tick.schema.json +0 -20
- package/.agents/schemas/lifecycle/notification.emitted.schema.json +0 -18
- package/.agents/schemas/lifecycle/pr.created.schema.json +0 -14
- package/.agents/schemas/lifecycle/retro.end.schema.json +0 -16
- package/.agents/schemas/lifecycle/retro.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/story.blocked.schema.json +0 -13
- package/.agents/schemas/lifecycle/story.dispatch.end.schema.json +0 -17
- package/.agents/schemas/lifecycle/story.dispatch.start.schema.json +0 -15
- package/.agents/schemas/lifecycle/story.merged.schema.json +0 -13
- package/.agents/scripts/check-gherkin-placeholders.js +0 -663
- package/.agents/scripts/check-lifecycle-doc-drift.js +0 -411
- package/.agents/scripts/lib/audit-suite/cli.js +0 -64
- package/.agents/scripts/lib/bootstrap/baselines-layout-migration.js +0 -202
- package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +0 -212
- package/.agents/scripts/lib/checks/baseline-drift-main-checkout.js +0 -104
- package/.agents/scripts/lib/checks/push-hook-parity.js +0 -106
- package/.agents/scripts/lib/checks/windows-coverage-noise-floor.js +0 -92
- package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +0 -81
- package/.agents/scripts/lib/checks/worktree-residue-biome.js +0 -55
- package/.agents/scripts/lib/crap-baseline-index.js +0 -46
- package/.agents/scripts/lib/crap-utils-incremental.js +0 -113
- package/.agents/scripts/lib/dynamic-workflow/capability.js +0 -396
- package/.agents/scripts/lib/feedback-loop/audit-results-graduator.js +0 -335
- package/.agents/scripts/lib/mutation/baseline-snapshot.js +0 -239
- package/.agents/scripts/lib/mutation/config-detector.js +0 -119
- package/.agents/scripts/lib/mutation/stryker-runner.js +0 -306
- package/.agents/scripts/lib/mutation/survivor-report.js +0 -160
- package/.agents/scripts/lib/observability/active-story-env.js +0 -170
- package/.agents/scripts/lib/observability/tool-trace-hook.js +0 -456
- package/.agents/scripts/lib/orchestration/acceptance-clusters.js +0 -111
- package/.agents/scripts/lib/orchestration/context-envelope.js +0 -277
- package/.agents/scripts/lib/orchestration/detectors-phase.js +0 -194
- package/.agents/scripts/lib/orchestration/lifecycle/bus.js +0 -309
- package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +0 -181
- package/.agents/scripts/lib/orchestration/lifecycle/ledger-writer.js +0 -229
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +0 -54
- package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +0 -344
- package/.agents/scripts/lib/orchestration/lint-baseline-service.js +0 -114
- package/.agents/scripts/lib/orchestration/pr-base-guard.js +0 -37
- package/.agents/scripts/lib/orchestration/resolves-token.js +0 -127
- package/.agents/scripts/lib/orchestration/spec-section-validator.js +0 -130
- package/.agents/scripts/lib/orchestration/story-close/emit-blocked.js +0 -55
- package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +0 -211
- package/.agents/scripts/lib/planning-corpus.js +0 -37
- package/.agents/scripts/lib/qa/coverage-report.js +0 -181
- package/.agents/scripts/lib/qa/propose-missing-test.js +0 -95
- package/.agents/scripts/lib/qa/qa-context-hydrator.js +0 -217
- package/.agents/scripts/lib/signals/detectors/index.js +0 -14
- package/.agents/scripts/lib/signals/detectors/retry.js +0 -253
- package/.agents/scripts/lib/signals/detectors/rework.js +0 -167
- package/.agents/scripts/lib/signals/read.js +0 -268
- package/.agents/scripts/lib/signals/span-tree.js +0 -291
- package/.agents/scripts/lib/story-lifecycle.js +0 -194
- package/.agents/scripts/lib/story-plan.js +0 -379
- package/.agents/scripts/lib/util/phase-timer-state.js +0 -72
- package/.agents/scripts/lib/util/phase-timer.js +0 -163
- package/.agents/scripts/lib/workers/combined-mi-crap-worker.js +0 -169
- package/.agents/scripts/lint-baseline.js +0 -507
- package/.agents/scripts/providers/github/prs.js +0 -103
- package/.agents/scripts/signals-view.js +0 -309
- package/.agents/scripts/story-plan.js +0 -370
- package/.agents/scripts/sync-branch-from-base.js +0 -149
- package/.agents/scripts/validate-docs-freshness.js +0 -314
- package/.agents/skills/core/diagnose-friction/SKILL.md +0 -78
- package/.agents/workflows/helpers/signals.md +0 -112
|
@@ -1,370 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/* node:coverage ignore file */
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* story-plan.js — QA helper for promote round-trips and `--emit-context`
|
|
6
|
-
* envelope emission.
|
|
7
|
-
*
|
|
8
|
-
* Canonical operator planning is `plan-context.js` + `plan-persist.js` (see
|
|
9
|
-
* `.agents/workflows/plan.md`). Use this CLI only for QA promote
|
|
10
|
-
* round-trips (`--body` / `--dry-run`) or test harnesses that need the
|
|
11
|
-
* standalone envelope shape.
|
|
12
|
-
*
|
|
13
|
-
* Operator planning:
|
|
14
|
-
* node .agents/scripts/plan-context.js --seed "…" | --seed-file <path> | --tickets <ids>
|
|
15
|
-
* node .agents/scripts/plan-persist.js --stories …
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import { readFile } from 'node:fs/promises';
|
|
19
|
-
import path from 'node:path';
|
|
20
|
-
import { parseArgs } from 'node:util';
|
|
21
|
-
import { runAsCli } from './lib/cli-utils.js';
|
|
22
|
-
import { PROJECT_ROOT, resolveConfig } from './lib/config-resolver.js';
|
|
23
|
-
import { exec as ghExec } from './lib/gh-exec.js';
|
|
24
|
-
import { Logger, routeAllOutputToStderr } from './lib/Logger.js';
|
|
25
|
-
import { TYPE_LABELS } from './lib/label-constants.js';
|
|
26
|
-
import { recordPlanInvocation } from './lib/orchestration/plan-metrics.js';
|
|
27
|
-
import { buildCorpusContext } from './lib/planning-corpus.js';
|
|
28
|
-
import { createProvider } from './lib/provider-factory.js';
|
|
29
|
-
import {
|
|
30
|
-
buildContextEnvelope,
|
|
31
|
-
loadBodyTemplate,
|
|
32
|
-
rankDuplicateCandidates,
|
|
33
|
-
readTechStackSummary,
|
|
34
|
-
shouldRefine,
|
|
35
|
-
synthesizeContractSections,
|
|
36
|
-
validateStoryBody,
|
|
37
|
-
} from './lib/story-plan.js';
|
|
38
|
-
|
|
39
|
-
const HELP = `\
|
|
40
|
-
Usage:
|
|
41
|
-
story-plan.js --emit-context (--seed "<seed>" | --seed-file <file>) \\
|
|
42
|
-
[--refine | --no-refine] [--pretty]
|
|
43
|
-
|
|
44
|
-
story-plan.js --body <file> [--acceptance <file>] [--verify <file>] \\
|
|
45
|
-
[--dry-run]
|
|
46
|
-
|
|
47
|
-
story-plan.js --help
|
|
48
|
-
|
|
49
|
-
Modes:
|
|
50
|
-
--emit-context Build the host-LLM authoring envelope and print it as
|
|
51
|
-
JSON on stdout. Use this first; the host LLM authors a
|
|
52
|
-
draft body using the envelope and the body template.
|
|
53
|
-
--body <file> Persist a pre-authored body. Validates shape (## Goal
|
|
54
|
-
and ## Changes present, no Epic: ref, an acceptance +
|
|
55
|
-
verify contract) and calls \`gh issue create\` with
|
|
56
|
-
type::story.
|
|
57
|
-
--acceptance <file>
|
|
58
|
-
JSON string[] of the Story's top-level acceptance
|
|
59
|
-
criteria. Authored once here — NOT mirrored into the
|
|
60
|
-
body by hand; the \`## Acceptance\` section is
|
|
61
|
-
synthesized from it, matching the story-author prompt.
|
|
62
|
-
--verify <file> JSON string[] of the Story's top-level verify entries,
|
|
63
|
-
each ending in a (unit|contract|e2e|validate) tier.
|
|
64
|
-
Synthesized into \`## Verify\` the same way.
|
|
65
|
-
--dry-run With --body: print the body and the gh argv that would
|
|
66
|
-
be invoked, then exit 0. No GitHub mutations.
|
|
67
|
-
|
|
68
|
-
Options:
|
|
69
|
-
--refine Force the idea-refinement hint on regardless of seed
|
|
70
|
-
length. Default heuristic: refine when seed < 200
|
|
71
|
-
chars.
|
|
72
|
-
--no-refine Force the idea-refinement hint off.
|
|
73
|
-
--pretty Pretty-print the JSON envelope.
|
|
74
|
-
`;
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Resolve the seed string from --seed or --seed-file. One of the two
|
|
78
|
-
* must be present in --emit-context mode.
|
|
79
|
-
*/
|
|
80
|
-
async function resolveSeed({ seed, seedFile }) {
|
|
81
|
-
if (seed && seedFile) {
|
|
82
|
-
throw new Error('Pass either --seed or --seed-file, not both.');
|
|
83
|
-
}
|
|
84
|
-
if (seed) return seed;
|
|
85
|
-
if (seedFile) return (await readFile(seedFile, 'utf8')).trim();
|
|
86
|
-
throw new Error(
|
|
87
|
-
'--emit-context requires --seed "<seed>" or --seed-file <file>.',
|
|
88
|
-
);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Fetch open Stories via the ticketing provider. The github provider
|
|
93
|
-
* exposes `listIssuesByLabel({ state, labels })`; other providers may
|
|
94
|
-
* not, so the call is guarded.
|
|
95
|
-
*/
|
|
96
|
-
async function fetchOpenStories(provider) {
|
|
97
|
-
if (typeof provider.listIssuesByLabel !== 'function') return [];
|
|
98
|
-
const issues = await provider.listIssuesByLabel({
|
|
99
|
-
state: 'open',
|
|
100
|
-
labels: TYPE_LABELS.STORY,
|
|
101
|
-
});
|
|
102
|
-
return issues.map((issue) => ({
|
|
103
|
-
id: issue.number ?? issue.id,
|
|
104
|
-
title: issue.title ?? '',
|
|
105
|
-
body: issue.body ?? '',
|
|
106
|
-
url: issue.html_url ?? issue.url ?? null,
|
|
107
|
-
}));
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Render the draft body argv for `gh issue create`. Returns the argv
|
|
112
|
-
* array; the caller decides whether to execute it (persist) or print
|
|
113
|
-
* it (--dry-run).
|
|
114
|
-
*/
|
|
115
|
-
function renderGhArgv({ title, bodyPath, labels, body = null }) {
|
|
116
|
-
// `--body-file` streams the authored file untouched; an inline `--body` is
|
|
117
|
-
// used only when persist synthesized sections into it (Story #4874), so the
|
|
118
|
-
// file on disk is no longer what gets created.
|
|
119
|
-
const source =
|
|
120
|
-
typeof body === 'string' ? ['--body', body] : ['--body-file', bodyPath];
|
|
121
|
-
const argv = ['issue', 'create', '--title', title, ...source];
|
|
122
|
-
for (const label of labels) {
|
|
123
|
-
argv.push('--label', label);
|
|
124
|
-
}
|
|
125
|
-
return argv;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Extract the H1 title from a body. Falls back to a sensible default
|
|
130
|
-
* when the body lacks one (host LLM should always emit `# <title>`).
|
|
131
|
-
*/
|
|
132
|
-
export function extractTitle(body) {
|
|
133
|
-
const m = body.match(/^#\s+(.+?)\s*$/m);
|
|
134
|
-
return m ? m[1].trim() : 'Untitled standalone Story';
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
async function runEmitContext({
|
|
138
|
-
values,
|
|
139
|
-
provider,
|
|
140
|
-
projectRoot,
|
|
141
|
-
config,
|
|
142
|
-
// Injectable stdout port so unit tests can capture the emitted envelope
|
|
143
|
-
// without stubbing the process-global stream (mirrors the `runPersist`
|
|
144
|
-
// pattern above — raw stdout writes corrupt the `node --test` runner's
|
|
145
|
-
// structured report stream).
|
|
146
|
-
write = (s) => process.stdout.write(s),
|
|
147
|
-
}) {
|
|
148
|
-
const seed = await resolveSeed({
|
|
149
|
-
seed: values.seed,
|
|
150
|
-
seedFile: values['seed-file'],
|
|
151
|
-
});
|
|
152
|
-
const override = values.refine ? 'on' : values['no-refine'] ? 'off' : null;
|
|
153
|
-
const refine = shouldRefine({ seed, override });
|
|
154
|
-
|
|
155
|
-
// Corpus lookup uses the raw (un-defaulted) docsContextFiles list, same
|
|
156
|
-
// as the `/deliver` per-Epic digest builder: `config.project` fills in
|
|
157
|
-
// the framework's default four-file set even when the operator
|
|
158
|
-
// configured nothing, so a null-vs-configured distinction requires
|
|
159
|
-
// reading `config.raw` directly.
|
|
160
|
-
const docsContextFiles = config?.raw?.project?.docsContextFiles ?? [];
|
|
161
|
-
// Resolve docsRoot against PROJECT_ROOT (not process.cwd()) so the
|
|
162
|
-
// corpus digest reads the project's actual docs directory regardless
|
|
163
|
-
// of the directory this CLI happens to be invoked from — matching the
|
|
164
|
-
// sibling resolution pattern in
|
|
165
|
-
// planning/authoring-context.js.
|
|
166
|
-
const docsRoot = path.resolve(
|
|
167
|
-
PROJECT_ROOT,
|
|
168
|
-
config?.project?.paths?.docsRoot ?? 'docs',
|
|
169
|
-
);
|
|
170
|
-
|
|
171
|
-
const [bodyTemplate, openStories, techStack, corpusContext] =
|
|
172
|
-
await Promise.all([
|
|
173
|
-
loadBodyTemplate(projectRoot),
|
|
174
|
-
fetchOpenStories(provider),
|
|
175
|
-
readTechStackSummary(projectRoot),
|
|
176
|
-
buildCorpusContext({ docsContextFiles, docsRoot }),
|
|
177
|
-
]);
|
|
178
|
-
|
|
179
|
-
const duplicateCandidates = rankDuplicateCandidates({
|
|
180
|
-
seed,
|
|
181
|
-
openStories,
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
const envelope = buildContextEnvelope({
|
|
185
|
-
seed,
|
|
186
|
-
refine,
|
|
187
|
-
bodyTemplate,
|
|
188
|
-
duplicateCandidates,
|
|
189
|
-
techStack,
|
|
190
|
-
corpusContext,
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
const json = values.pretty
|
|
194
|
-
? JSON.stringify(envelope, null, 2)
|
|
195
|
-
: JSON.stringify(envelope);
|
|
196
|
-
write(`${json}\n`);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Read an optional top-level contract array (`--acceptance` / `--verify`)
|
|
201
|
-
* from a JSON file. Absent flag → an empty list, so the flags stay optional
|
|
202
|
-
* for a body that already carries its own sections.
|
|
203
|
-
*
|
|
204
|
-
* @param {string|undefined} filePath
|
|
205
|
-
* @param {'acceptance'|'verify'} label
|
|
206
|
-
* @returns {Promise<string[]>}
|
|
207
|
-
*/
|
|
208
|
-
async function readContractList(filePath, label) {
|
|
209
|
-
if (!filePath) return [];
|
|
210
|
-
let parsed;
|
|
211
|
-
try {
|
|
212
|
-
parsed = JSON.parse(await readFile(filePath, 'utf8'));
|
|
213
|
-
} catch (err) {
|
|
214
|
-
throw new Error(
|
|
215
|
-
`Cannot read --${label} file "${filePath}": ${err.message}`,
|
|
216
|
-
);
|
|
217
|
-
}
|
|
218
|
-
if (!Array.isArray(parsed) || parsed.some((v) => typeof v !== 'string')) {
|
|
219
|
-
throw new Error(`--${label} file "${filePath}" must be a JSON string[].`);
|
|
220
|
-
}
|
|
221
|
-
return parsed;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
async function runPersist({
|
|
225
|
-
values,
|
|
226
|
-
provider,
|
|
227
|
-
dryRun,
|
|
228
|
-
// Injectable stdout port so unit tests can capture/silence the summary
|
|
229
|
-
// JSON without stubbing the process-global stream (raw stdout writes
|
|
230
|
-
// corrupt the `node --test` runner's structured report stream).
|
|
231
|
-
write = (s) => process.stdout.write(s),
|
|
232
|
-
}) {
|
|
233
|
-
const bodyPath = values.body;
|
|
234
|
-
if (!bodyPath) {
|
|
235
|
-
throw new Error('--body <file> is required in persist mode.');
|
|
236
|
-
}
|
|
237
|
-
const authored = await readFile(bodyPath, 'utf8');
|
|
238
|
-
const contract = {
|
|
239
|
-
acceptance: await readContractList(values.acceptance, 'acceptance'),
|
|
240
|
-
verify: await readContractList(values.verify, 'verify'),
|
|
241
|
-
};
|
|
242
|
-
const validation = validateStoryBody(authored, contract);
|
|
243
|
-
if (!validation.ok) {
|
|
244
|
-
throw new Error(
|
|
245
|
-
`Drafted body failed validation:\n - ${validation.errors.join('\n - ')}`,
|
|
246
|
-
);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// Story #4874: the author writes acceptance[] / verify[] once, at the
|
|
250
|
-
// ticket's top level, and persist renders their sections — never the
|
|
251
|
-
// author by hand.
|
|
252
|
-
const body = synthesizeContractSections(authored, contract);
|
|
253
|
-
const title = extractTitle(body);
|
|
254
|
-
const labels = [TYPE_LABELS.STORY];
|
|
255
|
-
const argv = renderGhArgv({
|
|
256
|
-
title,
|
|
257
|
-
bodyPath,
|
|
258
|
-
labels,
|
|
259
|
-
body: body === authored ? null : body,
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
if (dryRun) {
|
|
263
|
-
Logger.info('--- DRY RUN ---');
|
|
264
|
-
Logger.info(`Title: ${title}`);
|
|
265
|
-
Logger.info(`Labels: ${labels.join(', ')}`);
|
|
266
|
-
Logger.info(`gh argv: gh ${argv.join(' ')}`);
|
|
267
|
-
Logger.info('--- BODY ---');
|
|
268
|
-
Logger.info(body);
|
|
269
|
-
write(
|
|
270
|
-
`${JSON.stringify({ dryRun: true, title, labels, argv }, null, 2)}\n`,
|
|
271
|
-
);
|
|
272
|
-
return;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// Persist via the provider when available so I/O stays inside the
|
|
276
|
-
// injected ticketing surface. The GitHub provider's `createIssue`
|
|
277
|
-
// also adds the new Story to the configured Projects V2 board via
|
|
278
|
-
// the shared `addIssueToBoard` helper (Story #3822) — idempotent,
|
|
279
|
-
// non-fatal, no-op when no project number is configured — so board
|
|
280
|
-
// membership never depends on GitHub's "Auto-add to project"
|
|
281
|
-
// built-in workflow. Fall back to `gh issue create` only when the
|
|
282
|
-
// provider doesn't expose a createIssue analogue.
|
|
283
|
-
let issueNumber;
|
|
284
|
-
if (typeof provider.createIssue === 'function') {
|
|
285
|
-
const created = await provider.createIssue({ title, body, labels });
|
|
286
|
-
issueNumber = created.number ?? created.id;
|
|
287
|
-
} else {
|
|
288
|
-
// gh-exec returns { stdout, stderr, code }; parse the issue number
|
|
289
|
-
// from the URL.
|
|
290
|
-
const { stdout } = await ghExec({ args: argv });
|
|
291
|
-
const m = stdout.match(/\/issues\/(\d+)/);
|
|
292
|
-
if (!m) {
|
|
293
|
-
throw new Error(
|
|
294
|
-
`gh issue create did not return an issue URL; stdout was: ${stdout}`,
|
|
295
|
-
);
|
|
296
|
-
}
|
|
297
|
-
issueNumber = Number(m[1]);
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
write(`${JSON.stringify({ issueNumber, title, labels }, null, 2)}\n`);
|
|
301
|
-
Logger.info(`Next: /deliver ${issueNumber}`);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
/* node:coverage ignore next */
|
|
305
|
-
async function main() {
|
|
306
|
-
const { values } = parseArgs({
|
|
307
|
-
options: {
|
|
308
|
-
'emit-context': { type: 'boolean', default: false },
|
|
309
|
-
seed: { type: 'string' },
|
|
310
|
-
'seed-file': { type: 'string' },
|
|
311
|
-
body: { type: 'string' },
|
|
312
|
-
acceptance: { type: 'string' },
|
|
313
|
-
verify: { type: 'string' },
|
|
314
|
-
refine: { type: 'boolean', default: false },
|
|
315
|
-
'no-refine': { type: 'boolean', default: false },
|
|
316
|
-
pretty: { type: 'boolean', default: false },
|
|
317
|
-
'dry-run': { type: 'boolean', default: false },
|
|
318
|
-
help: { type: 'boolean', default: false },
|
|
319
|
-
},
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
if (values.help) {
|
|
323
|
-
process.stdout.write(HELP);
|
|
324
|
-
return;
|
|
325
|
-
}
|
|
326
|
-
if (values.refine && values['no-refine']) {
|
|
327
|
-
throw new Error('Pass either --refine or --no-refine, not both.');
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
const config = resolveConfig();
|
|
331
|
-
const provider = createProvider(config);
|
|
332
|
-
const projectRoot = PROJECT_ROOT;
|
|
333
|
-
|
|
334
|
-
if (values['emit-context']) {
|
|
335
|
-
// Reserve stdout for the JSON envelope so a captured file is
|
|
336
|
-
// unconditionally parseable by `JSON.parse`. Mirrors the contract
|
|
337
|
-
// `plan-context.js` enforces for its emit mode.
|
|
338
|
-
routeAllOutputToStderr();
|
|
339
|
-
// Plan-metrics ledger (#4474 PR1): standalone plans have no Epic, so
|
|
340
|
-
// `epicId: null` routes the stamp to the standalone stream
|
|
341
|
-
// (`temp/standalone/plan-metrics.json`) — same pattern as friction.
|
|
342
|
-
return recordPlanInvocation(
|
|
343
|
-
{ cli: 'story-plan', mode: 'emit-context', epicId: null, config },
|
|
344
|
-
() => runEmitContext({ values, provider, projectRoot, config }),
|
|
345
|
-
);
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
// Plan-metrics ledger (#4474 PR1): stamp entry/exit + mode.
|
|
349
|
-
return recordPlanInvocation(
|
|
350
|
-
{ cli: 'story-plan', mode: 'persist', epicId: null, config },
|
|
351
|
-
() =>
|
|
352
|
-
runPersist({
|
|
353
|
-
values,
|
|
354
|
-
provider,
|
|
355
|
-
dryRun: values['dry-run'],
|
|
356
|
-
}),
|
|
357
|
-
);
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
runAsCli(import.meta.url, main, { source: 'story-plan' });
|
|
361
|
-
|
|
362
|
-
// Test surface — exported so unit tests can drive the helpers
|
|
363
|
-
// without importing the CLI side.
|
|
364
|
-
export {
|
|
365
|
-
fetchOpenStories,
|
|
366
|
-
renderGhArgv,
|
|
367
|
-
resolveSeed,
|
|
368
|
-
runEmitContext,
|
|
369
|
-
runPersist,
|
|
370
|
-
};
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* sync-branch-from-base.js — operator-facing CLI wrapper around
|
|
5
|
-
* `lib/git/sync-from-base.js` (Story #2580).
|
|
6
|
-
*
|
|
7
|
-
* Use this from workflow markdown when the operator needs to sync a
|
|
8
|
-
* working branch with `origin/<baseBranch>` before opening a PR — for
|
|
9
|
-
* example as a pre-Phase-6 step in `/deliver` so the Epic→main PR
|
|
10
|
-
* opens with the latest `main` commits already integrated.
|
|
11
|
-
*
|
|
12
|
-
* For `/single-story-deliver`, the sync runs in-process inside
|
|
13
|
-
* `single-story-close.js`; this CLI is the operator surface for
|
|
14
|
-
* workflows that don't have a built-in close pipeline.
|
|
15
|
-
*
|
|
16
|
-
* Usage:
|
|
17
|
-
* node .agents/scripts/sync-branch-from-base.js \
|
|
18
|
-
* --branch <branchName> --base <baseBranch> [--cwd <path>]
|
|
19
|
-
*
|
|
20
|
-
* Exit codes:
|
|
21
|
-
* 0 — synced (`fast-forward`, `merge-commit`, or `noop-already-current`).
|
|
22
|
-
* 1 — fetch/merge failed or conflict; error message on stderr names the
|
|
23
|
-
* outcome kind and (for conflicts) the conflicting file list.
|
|
24
|
-
*
|
|
25
|
-
* The script does NOT post structured comments or flip ticket labels —
|
|
26
|
-
* it is a pure git operation. Recovery surfaces are the caller's
|
|
27
|
-
* responsibility (the workflow doc tells the operator what to do when
|
|
28
|
-
* this exits non-zero).
|
|
29
|
-
*/
|
|
30
|
-
|
|
31
|
-
import path from 'node:path';
|
|
32
|
-
import { parseArgs } from 'node:util';
|
|
33
|
-
import { runAsCli } from './lib/cli-utils.js';
|
|
34
|
-
import { resolveConfig } from './lib/config-resolver.js';
|
|
35
|
-
import { syncBranchFromBase } from './lib/git/sync-from-base.js';
|
|
36
|
-
import { gitSpawn, gitSync } from './lib/git-utils.js';
|
|
37
|
-
import { Logger } from './lib/Logger.js';
|
|
38
|
-
import { emitTerseResult } from './lib/observability/terse-result.js';
|
|
39
|
-
import { PROJECT_ROOT } from './lib/project-root.js';
|
|
40
|
-
|
|
41
|
-
const progress = Logger.createProgress('sync-branch-from-base', {
|
|
42
|
-
stderr: true,
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Core runner. Exported for tests so the CLI surface can be exercised
|
|
47
|
-
* without spawning a subprocess.
|
|
48
|
-
*
|
|
49
|
-
* @param {object} [opts]
|
|
50
|
-
* @param {string} [opts.branch] Branch name expected to be checked out.
|
|
51
|
-
* @param {string} [opts.base] Base branch name on origin (e.g. `main`).
|
|
52
|
-
* @param {string} [opts.cwd] Worktree or repo root. Defaults to PROJECT_ROOT.
|
|
53
|
-
* @param {typeof syncBranchFromBase} [opts.injectedSync] Test-only seam.
|
|
54
|
-
* @param {(cwd: string, ...args: string[]) => string} [opts.injectedGitSync]
|
|
55
|
-
* Test-only seam for the branch-name guard.
|
|
56
|
-
*/
|
|
57
|
-
export async function runSyncBranchFromBase(opts = {}) {
|
|
58
|
-
const parsed = opts.branch ? opts : parseArgv(process.argv.slice(2));
|
|
59
|
-
const branch = opts.branch ?? parsed.branch;
|
|
60
|
-
const base = opts.base ?? parsed.base;
|
|
61
|
-
if (!branch || !base) {
|
|
62
|
-
throw new Error(
|
|
63
|
-
'Usage: node sync-branch-from-base.js --branch <branchName> --base <baseBranch> [--cwd <path>]',
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
const cwd = path.resolve(opts.cwd ?? parsed.cwd ?? PROJECT_ROOT);
|
|
67
|
-
|
|
68
|
-
// Guard: refuse to sync if the active branch doesn't match the
|
|
69
|
-
// operator's intent. A mismatched checkout would merge `origin/<base>`
|
|
70
|
-
// into the wrong branch — silently — and only surface as a confusing
|
|
71
|
-
// diff later. Skipped when injectedGitSync is set so tests can run
|
|
72
|
-
// without a real git repo.
|
|
73
|
-
const gitSyncFn = opts.injectedGitSync ?? gitSync;
|
|
74
|
-
let activeBranch = null;
|
|
75
|
-
try {
|
|
76
|
-
activeBranch = gitSyncFn(cwd, 'rev-parse', '--abbrev-ref', 'HEAD');
|
|
77
|
-
} catch (err) {
|
|
78
|
-
throw new Error(
|
|
79
|
-
`[sync-branch-from-base] Could not resolve active branch in ${cwd}: ${err?.message ?? err}`,
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
if (activeBranch !== branch) {
|
|
83
|
-
throw new Error(
|
|
84
|
-
`[sync-branch-from-base] Active branch is "${activeBranch}" but --branch is "${branch}". ` +
|
|
85
|
-
`Check out ${branch} first, or pass --cwd to the correct worktree.`,
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
progress('INIT', `Syncing ${branch} from origin/${base} in ${cwd}...`);
|
|
90
|
-
const syncFn = opts.injectedSync ?? syncBranchFromBase;
|
|
91
|
-
const result = await syncFn({
|
|
92
|
-
cwd,
|
|
93
|
-
baseBranch: base,
|
|
94
|
-
log: (tag, msg) => progress(tag, msg),
|
|
95
|
-
gitSpawn,
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
// Story #4685 — full detail to a temp log; emit a single summary line.
|
|
99
|
-
// Story #4794 — resolve the config so the log honours `project.paths.tempRoot`
|
|
100
|
-
// instead of the hardcoded `<cwd>/temp` this used to join. `resolveConfig`
|
|
101
|
-
// degrades to the framework defaults when no `.agentrc.json` is present, so
|
|
102
|
-
// a zero-config invocation needs no guard here.
|
|
103
|
-
emitTerseResult({
|
|
104
|
-
label: 'SYNC RESULT',
|
|
105
|
-
result,
|
|
106
|
-
scope: branch,
|
|
107
|
-
config: resolveConfig({ cwd }),
|
|
108
|
-
summary: { branch, base, synced: result.synced, kind: result.kind },
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
if (!result.synced) {
|
|
112
|
-
const detail =
|
|
113
|
-
result.kind === 'conflict'
|
|
114
|
-
? `: ${result.conflictFiles?.join(', ') ?? '(no file list)'}`
|
|
115
|
-
: result.stderr
|
|
116
|
-
? `: ${result.stderr.slice(0, 200)}`
|
|
117
|
-
: '';
|
|
118
|
-
throw new Error(
|
|
119
|
-
`[sync-branch-from-base] sync failed (${result.kind})${detail}`,
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
progress('DONE', `✅ Synced ${branch} from origin/${base} (${result.kind}).`);
|
|
123
|
-
return { success: true, result };
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Tiny argv parser scoped to this CLI's three flags. Exported for
|
|
128
|
-
* tests.
|
|
129
|
-
*/
|
|
130
|
-
export function parseArgv(argv) {
|
|
131
|
-
const { values } = parseArgs({
|
|
132
|
-
args: argv,
|
|
133
|
-
options: {
|
|
134
|
-
branch: { type: 'string' },
|
|
135
|
-
base: { type: 'string' },
|
|
136
|
-
cwd: { type: 'string' },
|
|
137
|
-
},
|
|
138
|
-
strict: false,
|
|
139
|
-
});
|
|
140
|
-
return {
|
|
141
|
-
branch: values.branch ?? null,
|
|
142
|
-
base: values.base ?? null,
|
|
143
|
-
cwd: values.cwd ?? null,
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
runAsCli(import.meta.url, runSyncBranchFromBase, {
|
|
148
|
-
source: 'sync-branch-from-base',
|
|
149
|
-
});
|