mandrel 1.59.0 → 1.61.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 +86 -44
- package/.agents/docs/SDLC.md +135 -141
- package/.agents/docs/configuration.md +77 -20
- package/.agents/docs/quality-gates.md +796 -0
- package/.agents/docs/workflows.md +6 -9
- package/.agents/instructions.md +12 -11
- package/.agents/personas/architect.md +1 -1
- package/.agents/personas/product.md +1 -1
- package/.agents/personas/project-manager.md +14 -14
- package/.agents/personas/technical-writer.md +1 -1
- package/.agents/rules/changelog-style.md +5 -5
- package/.agents/rules/git-conventions.md +3 -3
- package/.agents/runtime-deps.json +2 -2
- package/.agents/schemas/agentrc.schema.json +3 -3
- package/.agents/schemas/dispatch-manifest.json +4 -4
- package/.agents/schemas/epic-spec.schema.json +15 -45
- package/.agents/schemas/lifecycle/README.md +1 -1
- package/.agents/schemas/lifecycle/story.dispatch.end.schema.json +1 -1
- package/.agents/schemas/lifecycle/story.dispatch.start.schema.json +1 -1
- package/.agents/schemas/lifecycle/story.heartbeat.schema.json +1 -1
- package/.agents/schemas/validation-evidence.schema.json +1 -1
- package/.agents/scripts/README.md +2 -2
- package/.agents/scripts/acceptance-eval.js +1 -1
- package/.agents/scripts/acceptance-spec-reconciler.js +2 -2
- package/.agents/scripts/agents-bootstrap-github.js +23 -119
- package/.agents/scripts/analyze-execution.js +2 -2
- package/.agents/scripts/audit-to-stories.js +1 -1
- package/.agents/scripts/check-doc-links.js +2 -3
- package/.agents/scripts/diagnose-friction.js +1 -1
- package/.agents/scripts/dispatcher.js +2 -2
- package/.agents/scripts/drain-pending-cleanup.js +1 -1
- package/.agents/scripts/epic-audit-prepare.js +3 -3
- package/.agents/scripts/epic-deliver-note-intervention.js +2 -2
- package/.agents/scripts/epic-deliver-preflight.js +6 -6
- package/.agents/scripts/epic-deliver-prepare.js +1 -1
- package/.agents/scripts/epic-execute-record-wave.js +4 -4
- package/.agents/scripts/epic-plan-healthcheck.js +6 -10
- package/.agents/scripts/epic-plan-spec-validate.js +1 -1
- package/.agents/scripts/epic-reconcile.js +11 -29
- package/.agents/scripts/evidence-gate.js +1 -1
- package/.agents/scripts/generate-workflows-doc.js +1 -1
- package/.agents/scripts/hierarchy-gate.js +7 -11
- package/.agents/scripts/lib/ITicketingProvider.js +1 -1
- package/.agents/scripts/lib/audit-suite/selector.js +1 -1
- package/.agents/scripts/lib/audit-to-stories/seed-epic-from-findings.js +2 -2
- package/.agents/scripts/lib/baseline-snapshot.js +7 -7
- package/.agents/scripts/lib/bdd-runner-detect.js +1 -1
- package/.agents/scripts/lib/bdd-scenario-scanner.js +3 -3
- package/.agents/scripts/lib/bootstrap/baselines-layout-migration.js +1 -1
- package/.agents/scripts/lib/bootstrap/branch-protection.js +1 -1
- package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +47 -1
- package/.agents/scripts/lib/bootstrap/commit-push.js +2 -2
- package/.agents/scripts/lib/bootstrap/gh-preflight.js +7 -9
- package/.agents/scripts/lib/bootstrap/manifest.js +21 -1
- package/.agents/scripts/lib/bootstrap/merge-methods.js +31 -16
- package/.agents/scripts/lib/bootstrap/project-bootstrap.js +32 -11
- package/.agents/scripts/lib/codebase-snapshot.js +1 -1
- package/.agents/scripts/lib/config/explain.js +1 -1
- package/.agents/scripts/lib/config/runners.js +2 -2
- package/.agents/scripts/lib/config/runtime.js +1 -1
- package/.agents/scripts/lib/config/sync-agentrc.js +1 -1
- package/.agents/scripts/lib/config/temp-paths.js +2 -2
- package/.agents/scripts/lib/config-settings-schema-delivery.js +2 -2
- package/.agents/scripts/lib/config-settings-schema-quality.js +1 -1
- package/.agents/scripts/lib/config-settings-schema.js +3 -3
- package/.agents/scripts/lib/detect-package-manager.js +72 -0
- package/.agents/scripts/lib/duplicate-search.js +1 -1
- package/.agents/scripts/lib/dynamic-workflow/capability.js +1 -1
- package/.agents/scripts/lib/epic-plan-clarity.js +1 -1
- package/.agents/scripts/lib/epic-plan-ideation.js +1 -1
- package/.agents/scripts/lib/errors/index.js +4 -4
- package/.agents/scripts/lib/feedback-loop/memory-freshness.js +1 -1
- package/.agents/scripts/lib/feedback-loop/prior-feedback-fetcher.js +1 -1
- package/.agents/scripts/lib/findings/classify-finding.js +1 -1
- package/.agents/scripts/lib/findings/promote-finding.js +10 -10
- package/.agents/scripts/lib/label-constants.js +3 -4
- package/.agents/scripts/lib/label-taxonomy.js +5 -10
- package/.agents/scripts/lib/onboard/detect-stack.js +10 -10
- package/.agents/scripts/lib/onboard/init-tail.js +218 -0
- package/.agents/scripts/lib/onboard/scaffold-docs.js +18 -3
- package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +1 -1
- package/.agents/scripts/lib/orchestration/code-review.js +5 -5
- package/.agents/scripts/lib/orchestration/context-hydration-engine.js +8 -9
- package/.agents/scripts/lib/orchestration/dependency-analyzer.js +3 -3
- package/.agents/scripts/lib/orchestration/detectors-phase.js +2 -2
- package/.agents/scripts/lib/orchestration/dispatch-engine.js +30 -38
- package/.agents/scripts/lib/orchestration/dispatch-pipeline.js +9 -25
- package/.agents/scripts/lib/orchestration/epic-cleanup.js +1 -1
- package/.agents/scripts/lib/orchestration/epic-deliver-lease-guard.js +8 -8
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/creation.js +1 -1
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/dag.js +7 -21
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/diagnostics.js +3 -3
- package/.agents/scripts/lib/orchestration/epic-plan-lease-guard.js +26 -13
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/plan-epic.js +1 -1
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/prompts.js +1 -1
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/run-spec-phase.js +2 -2
- package/.agents/scripts/lib/orchestration/epic-plan-state-store.js +1 -1
- package/.agents/scripts/lib/orchestration/epic-run-state-store.js +3 -3
- package/.agents/scripts/lib/orchestration/epic-runner/concurrency-gate.js +4 -4
- package/.agents/scripts/lib/orchestration/epic-runner/deliver-phases.js +3 -3
- package/.agents/scripts/lib/orchestration/epic-runner/phases/build-wave-dag.js +6 -21
- package/.agents/scripts/lib/orchestration/epic-runner/phases/snapshot.js +7 -7
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/composition.js +1 -1
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/signals.js +2 -2
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/transport.js +4 -4
- package/.agents/scripts/lib/orchestration/epic-runner/story-launcher.js +4 -4
- package/.agents/scripts/lib/orchestration/epic-runner/story-run-progress-writer.js +8 -8
- package/.agents/scripts/lib/orchestration/epic-runner/sub-agent-return.js +4 -4
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-apply.js +7 -15
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-diff.js +72 -41
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-ops.js +2 -4
- package/.agents/scripts/lib/orchestration/file-assumptions.js +2 -2
- package/.agents/scripts/lib/orchestration/finalize/close-planning-tickets.js +1 -1
- package/.agents/scripts/lib/orchestration/finalize/open-or-locate-pr.js +2 -2
- package/.agents/scripts/lib/orchestration/finalize/sanitize-skip-ci.js +1 -1
- package/.agents/scripts/lib/orchestration/lease-guard-shared.js +3 -3
- package/.agents/scripts/lib/orchestration/lifecycle/emit-story-dispatch-end.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-armer.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-predicate.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/branch-cleaner.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/finalizer.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/index.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/merge-watcher.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/notify-dispatcher.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +1 -1
- package/.agents/scripts/lib/orchestration/manifest-builder.js +5 -5
- package/.agents/scripts/lib/orchestration/parked-follow-ons.js +2 -2
- package/.agents/scripts/lib/orchestration/plan-runner/plan-router.js +5 -5
- package/.agents/scripts/lib/orchestration/post-merge/phases/ticket-closure.js +3 -3
- package/.agents/scripts/lib/orchestration/preflight-cache.js +1 -1
- package/.agents/scripts/lib/orchestration/recurring-failure-detector.js +1 -1
- package/.agents/scripts/lib/orchestration/retro/phases/compose-body.js +1 -1
- package/.agents/scripts/lib/orchestration/retro/phases/gather-signals.js +2 -2
- package/.agents/scripts/lib/orchestration/retro-runner.js +3 -3
- package/.agents/scripts/lib/orchestration/review-depth.js +1 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +1 -1
- package/.agents/scripts/lib/orchestration/spec-freshness.js +1 -1
- package/.agents/scripts/lib/orchestration/spec-renderer.js +36 -73
- package/.agents/scripts/lib/orchestration/spec-section-validator.js +1 -1
- package/.agents/scripts/lib/orchestration/story-close/baseline-friction-body.js +1 -1
- package/.agents/scripts/lib/orchestration/story-close/phases/locked-pipeline.js +2 -2
- package/.agents/scripts/lib/orchestration/task-body-validator.js +6 -6
- package/.agents/scripts/lib/orchestration/ticket-lease.js +1 -1
- package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +2 -2
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +1 -10
- package/.agents/scripts/lib/orchestration/ticket-validator.js +25 -70
- package/.agents/scripts/lib/orchestration/ticketing/bulk.js +5 -12
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +8 -8
- package/.agents/scripts/lib/orchestration/ticketing/state.js +3 -3
- package/.agents/scripts/lib/orchestration/wave-record-notifications.js +2 -2
- package/.agents/scripts/lib/orchestration/wave-record-projection.js +1 -1
- package/.agents/scripts/lib/plan-phase-cleanup.js +1 -1
- package/.agents/scripts/lib/preflight-runner.js +1 -1
- package/.agents/scripts/lib/presentation/dispatch-manifest-render.js +4 -5
- package/.agents/scripts/lib/presentation/manifest-builder.js +28 -34
- package/.agents/scripts/lib/presentation/manifest-formatter.js +3 -4
- package/.agents/scripts/lib/presentation/manifest-helpers.js +1 -1
- package/.agents/scripts/lib/presentation/manifest-procedures.js +4 -4
- package/.agents/scripts/lib/presentation/manifest-render-waves.js +4 -23
- package/.agents/scripts/lib/presentation/manifest-renderer.js +1 -1
- package/.agents/scripts/lib/presentation/manifest-story-views.js +2 -11
- package/.agents/scripts/lib/runtime-deps/preflight.js +6 -6
- package/.agents/scripts/lib/signals/schema.js +1 -1
- package/.agents/scripts/lib/spec/index.js +1 -1
- package/.agents/scripts/lib/spec/loader.js +2 -2
- package/.agents/scripts/lib/spec/state.js +7 -16
- package/.agents/scripts/lib/story-init/context-resolver.js +3 -3
- package/.agents/scripts/lib/story-init/state-transitioner.js +2 -2
- package/.agents/scripts/lib/story-init/task-graph-builder.js +7 -7
- package/.agents/scripts/lib/story-lifecycle.js +8 -8
- package/.agents/scripts/lib/story-plan.js +1 -1
- package/.agents/scripts/lib/templates/decomposer-prompts.js +59 -52
- package/.agents/scripts/lib/wave-runner/tick.js +1 -1
- package/.agents/scripts/lib/worktree/node-modules-strategy.js +5 -2
- package/.agents/scripts/lifecycle-emit-story-dispatch.js +1 -1
- package/.agents/scripts/lifecycle-emit.js +1 -1
- package/.agents/scripts/providers/github/board-add.js +1 -1
- package/.agents/scripts/providers/github/errors.js +1 -1
- package/.agents/scripts/providers/github/mappers.js +2 -2
- package/.agents/scripts/providers/github/tickets.js +4 -4
- package/.agents/scripts/resync-status-column.js +1 -1
- package/.agents/scripts/retro-run.js +2 -2
- package/.agents/scripts/run-lint.js +1 -1
- package/.agents/scripts/single-story-init.js +1 -1
- package/.agents/scripts/stories-wave-tick.js +5 -5
- package/.agents/scripts/story-close.js +1 -1
- package/.agents/scripts/story-init.js +13 -16
- package/.agents/scripts/story-phase.js +5 -5
- package/.agents/scripts/story-plan.js +3 -3
- package/.agents/scripts/sync-branch-from-base.js +1 -1
- package/.agents/scripts/validate-docs-freshness.js +1 -1
- package/.agents/scripts/wave-tick.js +1 -1
- package/.agents/skills/core/analyze-execution/SKILL.md +2 -2
- package/.agents/skills/core/epic-plan-consolidate/SKILL.md +21 -26
- package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +23 -56
- package/.agents/skills/core/epic-plan-spec-author/SKILL.md +4 -4
- package/.agents/skills/core/hydrate-context/SKILL.md +2 -2
- package/.agents/skills/core/idea-refinement/SKILL.md +4 -4
- package/.agents/skills/core/knowledge-transfer/SKILL.md +2 -2
- package/.agents/skills/core/planning-and-task-breakdown/SKILL.md +1 -1
- package/.agents/skills/core/scope-triage/SKILL.md +9 -10
- package/.agents/skills/core/using-agent-skills/SKILL.md +1 -1
- package/.agents/skills/skills.index.json +7 -7
- package/.agents/templates/agent-protocol.md +2 -2
- package/.agents/workflows/agents-update.md +16 -31
- package/.agents/workflows/audit-architecture.md +2 -2
- package/.agents/workflows/audit-clean-code.md +2 -2
- package/.agents/workflows/audit-dependencies.md +1 -1
- package/.agents/workflows/audit-devops.md +1 -1
- package/.agents/workflows/audit-documentation.md +2 -2
- package/.agents/workflows/audit-lighthouse.md +1 -1
- package/.agents/workflows/audit-performance.md +2 -2
- package/.agents/workflows/audit-privacy.md +1 -1
- package/.agents/workflows/audit-quality.md +2 -2
- package/.agents/workflows/audit-security.md +2 -2
- package/.agents/workflows/audit-seo.md +1 -1
- package/.agents/workflows/audit-sre.md +1 -1
- package/.agents/workflows/audit-to-stories.md +10 -10
- package/.agents/workflows/audit-ux-ui.md +1 -1
- package/.agents/workflows/deliver.md +85 -0
- package/.agents/workflows/explain.md +3 -3
- package/.agents/workflows/git-merge-pr.md +1 -1
- package/.agents/workflows/git-pr-all.md +13 -10
- package/.agents/workflows/git-push.md +6 -3
- package/.agents/workflows/helpers/_merge-conflict-template.md +1 -1
- package/.agents/workflows/helpers/acceptance-self-eval.md +1 -1
- package/.agents/workflows/helpers/agents-sync-config.md +3 -2
- package/.agents/workflows/helpers/code-review.md +5 -5
- package/.agents/workflows/{epic-deliver.md → helpers/deliver-epic.md} +43 -43
- package/.agents/workflows/{story-deliver.md → helpers/deliver-stories.md} +25 -25
- package/.agents/workflows/helpers/diagnose.md +1 -1
- package/.agents/workflows/helpers/epic-audit.md +6 -6
- package/.agents/workflows/helpers/epic-deliver-story.md +13 -13
- package/.agents/workflows/helpers/epic-plan-decompose.md +23 -23
- package/.agents/workflows/helpers/epic-plan-spec.md +6 -6
- package/.agents/workflows/helpers/epic-testing.md +3 -3
- package/.agents/workflows/helpers/parallel-tooling.md +1 -1
- package/.agents/workflows/{epic-plan.md → helpers/plan-epic.md} +84 -84
- package/.agents/workflows/{story-plan.md → helpers/plan-story.md} +43 -43
- package/.agents/workflows/helpers/signals.md +1 -1
- package/.agents/workflows/helpers/single-story-deliver.md +11 -11
- package/.agents/workflows/helpers/worktree-lifecycle.md +18 -18
- package/.agents/workflows/plan.md +131 -0
- package/.agents/workflows/qa-explore.md +1 -1
- package/.agents/workflows/qa-run-harness.md +1 -1
- package/README.md +19 -39
- package/bin/mandrel.js +235 -16
- package/docs/CHANGELOG.md +1173 -0
- package/lib/cli/doctor.js +45 -3
- package/lib/cli/init.js +97 -36
- package/lib/cli/registry.js +41 -145
- package/lib/cli/sync.js +122 -23
- package/lib/cli/uninstall.js +42 -7
- package/lib/cli/update.js +524 -210
- package/lib/cli/version-helpers.js +59 -0
- package/package.json +7 -6
- package/.agents/scripts/lib/orchestration/reconciler.js +0 -137
- package/.agents/workflows/onboard.md +0 -208
- package/lib/cli/__tests__/migrate.test.js +0 -268
- package/lib/cli/__tests__/sync-local-zone.test.js +0 -247
- package/lib/cli/__tests__/sync.test.js +0 -372
- package/lib/cli/__tests__/update-major.test.js +0 -217
- package/lib/cli/__tests__/update.test.js +0 -696
- package/lib/cli/__tests__/version-check.test.js +0 -398
- package/lib/migrations/__tests__/index.test.js +0 -216
|
@@ -205,7 +205,7 @@ const GITHUB_SCHEMA = {
|
|
|
205
205
|
};
|
|
206
206
|
|
|
207
207
|
// ---------------------------------------------------------------------------
|
|
208
|
-
// planning.* — inputs to /
|
|
208
|
+
// planning.* — inputs to /plan
|
|
209
209
|
// ---------------------------------------------------------------------------
|
|
210
210
|
|
|
211
211
|
/**
|
|
@@ -224,7 +224,7 @@ const PLANNING_CONTEXT_SCHEMA = {
|
|
|
224
224
|
|
|
225
225
|
/**
|
|
226
226
|
* Story #2634 — `planning.codebaseSnapshot` controls the structural
|
|
227
|
-
* view of the consumer repo threaded into `/
|
|
227
|
+
* view of the consumer repo threaded into `/plan` Phase 7 spec
|
|
228
228
|
* authoring. Absent / partial entries resolve to defaults inside
|
|
229
229
|
* `lib/codebase-snapshot.js#resolveSnapshotConfig` — the schema only
|
|
230
230
|
* enforces shape (correct enum value, well-formed glob arrays).
|
|
@@ -287,7 +287,7 @@ const PLANNING_SCHEMA = {
|
|
|
287
287
|
};
|
|
288
288
|
|
|
289
289
|
// ---------------------------------------------------------------------------
|
|
290
|
-
// delivery.* — /
|
|
290
|
+
// delivery.* — /deliver + story-deliver consume. The full block of
|
|
291
291
|
// per-key sub-schemas lives in `config-settings-schema-delivery.js` (refs
|
|
292
292
|
// #3457); DELIVERY_SCHEMA is imported above and referenced unchanged below.
|
|
293
293
|
// ---------------------------------------------------------------------------
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* detect-package-manager — shared lockfile-probe helper (Story #4048 B3).
|
|
3
|
+
*
|
|
4
|
+
* Five independent copies of this lockfile probe existed across the codebase:
|
|
5
|
+
* - `lib/cli/update.js#detectPackageManager`
|
|
6
|
+
* - `lib/bootstrap/project-bootstrap.js#detectPackageManager`
|
|
7
|
+
* - `lib/runtime-deps/preflight.js#detectPackageManager`
|
|
8
|
+
* - `lib/onboard/detect-stack.js#detectPackageManager`
|
|
9
|
+
* - `lib/worktree/node-modules-strategy.js#selectInstallCommand` (inline)
|
|
10
|
+
*
|
|
11
|
+
* This module is the single authoritative implementation. It uses the
|
|
12
|
+
* strictest semantics from the prior copies: detects `bun` in addition to
|
|
13
|
+
* pnpm/yarn/npm, returns `null` when the directory has no Node manifest at
|
|
14
|
+
* all (not even `package.json`), and optionally reports `workspaceRoot` for
|
|
15
|
+
* pnpm (the `update.js` caller's unique requirement).
|
|
16
|
+
*
|
|
17
|
+
* All callers must handle `null` explicitly — it means the directory carries
|
|
18
|
+
* no recognizable Node toolchain, so callers that need a concrete fallback
|
|
19
|
+
* should coerce: `detectPm(root) ?? 'npm'`.
|
|
20
|
+
*
|
|
21
|
+
* Injectable seams: the `exists` parameter replaces `fs.existsSync` so
|
|
22
|
+
* callers can drive the function with an in-memory fixture in unit tests.
|
|
23
|
+
*
|
|
24
|
+
* Builtins only — this module runs before third-party packages are
|
|
25
|
+
* guaranteed to be present and is also imported from the worktree and
|
|
26
|
+
* runtime-deps preflight guards.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import fs from 'node:fs';
|
|
30
|
+
import path from 'node:path';
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Detect the package manager from lockfile and manifest presence.
|
|
34
|
+
*
|
|
35
|
+
* Precedence: `pnpm-lock.yaml` > `yarn.lock` > `bun.lockb` >
|
|
36
|
+
* `package-lock.json` > `package.json` (npm without a lockfile yet) > `null`.
|
|
37
|
+
*
|
|
38
|
+
* @param {string} root - Absolute directory to probe (consumer project root).
|
|
39
|
+
* @param {(p: string) => boolean} [exists=fs.existsSync] - Path existence probe.
|
|
40
|
+
* @returns {'pnpm'|'yarn'|'bun'|'npm'|null}
|
|
41
|
+
*/
|
|
42
|
+
export function detectPackageManager(root, exists = fs.existsSync) {
|
|
43
|
+
if (exists(path.join(root, 'pnpm-lock.yaml'))) return 'pnpm';
|
|
44
|
+
if (exists(path.join(root, 'yarn.lock'))) return 'yarn';
|
|
45
|
+
if (exists(path.join(root, 'bun.lockb'))) return 'bun';
|
|
46
|
+
if (exists(path.join(root, 'package-lock.json'))) return 'npm';
|
|
47
|
+
if (exists(path.join(root, 'package.json'))) return 'npm';
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Detect the package manager and whether the directory is a pnpm workspace
|
|
53
|
+
* root. The `workspaceRoot` flag is `true` only for pnpm when
|
|
54
|
+
* `pnpm-workspace.yaml` is present alongside the lockfile — the signal that
|
|
55
|
+
* `pnpm add` must carry `-w` to target the workspace-root manifest.
|
|
56
|
+
*
|
|
57
|
+
* Used by `lib/cli/update.js` which needs both pieces of information to
|
|
58
|
+
* construct the correct install command.
|
|
59
|
+
*
|
|
60
|
+
* @param {string} root - Absolute directory to probe.
|
|
61
|
+
* @param {(p: string) => boolean} [exists=fs.existsSync] - Path existence probe.
|
|
62
|
+
* @returns {{ packageManager: 'pnpm'|'yarn'|'bun'|'npm', workspaceRoot: boolean }}
|
|
63
|
+
*/
|
|
64
|
+
export function detectPackageManagerWithWorkspace(
|
|
65
|
+
root,
|
|
66
|
+
exists = fs.existsSync,
|
|
67
|
+
) {
|
|
68
|
+
const pm = detectPackageManager(root, exists) ?? 'npm';
|
|
69
|
+
const workspaceRoot =
|
|
70
|
+
pm === 'pnpm' && exists(path.join(root, 'pnpm-workspace.yaml'));
|
|
71
|
+
return { packageManager: pm, workspaceRoot };
|
|
72
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* duplicate-search.js — Cross-Epic Duplicate Detection
|
|
3
3
|
*
|
|
4
|
-
* Used by `/
|
|
4
|
+
* Used by `/plan` Phase 2 (s-plan-ideation) to surface open Epics
|
|
5
5
|
* whose scope overlaps with a sharpened one-pager before a new Epic is
|
|
6
6
|
* created. Returns ranked candidates with an overlap score and URL so
|
|
7
7
|
* the host LLM can pause for HITL confirmation.
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
*
|
|
27
27
|
* Both paths MUST emit the identical per-lens report contract
|
|
28
28
|
* (`{{auditOutputDir}}/<lens>-results.md`), so downstream consumers
|
|
29
|
-
* (`/
|
|
29
|
+
* (`/deliver` Phase 4 epic-audit, `audit-to-stories`) are agnostic to
|
|
30
30
|
* which path produced it.
|
|
31
31
|
*
|
|
32
32
|
* ## Why this is capability-degradation, not a contract shim
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* Verdict rule: `clear` requires **both** (a) ≥ 4 of 5 canonical sections
|
|
11
11
|
* present, **and** (b) the **Acceptance Criteria** section present. The
|
|
12
12
|
* Acceptance-Criteria requirement is load-bearing: a downstream
|
|
13
|
-
* `/
|
|
13
|
+
* `/deliver` start gate and the close-time acceptance-spec reconciler
|
|
14
14
|
* both assume the Epic carries acceptance criteria, so a gate that passed an
|
|
15
15
|
* Epic with no Acceptance Criteria (the pre-Story-#3910 `≥ 4 of 5` behaviour)
|
|
16
16
|
* advertised a clarity guarantee it did not provide. AC is now a required
|
|
@@ -52,10 +52,10 @@ export class GhVersionError extends Error {
|
|
|
52
52
|
/**
|
|
53
53
|
* Raised when a framework runtime dependency (e.g. `ajv`) cannot be
|
|
54
54
|
* resolved from the consumer's `node_modules/`. Surfaces during the
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
55
|
+
* `agents-bootstrap-github` preflight to redirect operators to the
|
|
56
|
+
* correct remediation (`mandrel init` for new projects, or
|
|
57
|
+
* `npm install mandrel` for existing ones). `missing` carries the
|
|
58
|
+
* package specifiers that failed to resolve so the CLI can render an
|
|
59
59
|
* actionable hint.
|
|
60
60
|
*/
|
|
61
61
|
export class MissingRuntimeDepsError extends Error {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* memory-freshness.js — walker + verifier for the `/
|
|
2
|
+
* memory-freshness.js — walker + verifier for the `/plan` Phase 0
|
|
3
3
|
* memory-freshness pre-flight. Story #2557 / Epic #2547. Tech Spec #2550.
|
|
4
4
|
*
|
|
5
5
|
* Walks every `.md` file under a memory directory (typically
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* prior-feedback-fetcher.js — gh-CLI-backed fetcher for open meta feedback
|
|
3
|
-
* issues that feed the `/
|
|
3
|
+
* issues that feed the `/plan` Phase 0 planner context.
|
|
4
4
|
*
|
|
5
5
|
* Story #2554 / Epic #2547. Tech Spec #2550 specifies that the fetcher MUST
|
|
6
6
|
* return open issues carrying the `meta::framework-gap` and
|
|
@@ -51,7 +51,7 @@ export const FINDING_CLASSES = Object.freeze([
|
|
|
51
51
|
/**
|
|
52
52
|
* Class → label-set routing table. Each class maps to exactly one label set.
|
|
53
53
|
* `tooling-dx` is the framework-gap path: it carries `meta::framework-gap` so
|
|
54
|
-
* the `/
|
|
54
|
+
* the `/plan` Phase 0 feedback fetcher surfaces it to the planner.
|
|
55
55
|
*/
|
|
56
56
|
const CLASS_TO_LABELS = Object.freeze({
|
|
57
57
|
'product-bug': [FOCUS_LABELS.PRODUCT],
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* tail of the exploratory-QA Triage path: once an operator has dispositioned a
|
|
6
6
|
* session's ledger items (see `.agents/schemas/qa-ledger.schema.json` and
|
|
7
7
|
* `lib/qa/qa-session.js`), the still-untriaged backlog is clustered and each
|
|
8
|
-
* cluster is promoted to a follow-up ticket — a single Story (via `/
|
|
9
|
-
* for a tight, one-deliverable cluster, or an Epic (via `/
|
|
8
|
+
* cluster is promoted to a follow-up ticket — a single Story (via `/plan`)
|
|
9
|
+
* for a tight, one-deliverable cluster, or an Epic (via `/plan --idea`) for
|
|
10
10
|
* a broad cluster that spans multiple coverage surfaces. Each contributing
|
|
11
11
|
* ledger item then has the resulting `routedTo` issue link written back onto it
|
|
12
12
|
* so a resume run sees the item as filed rather than re-promoting it.
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* Pure orchestration: **no network I/O lives here.** Every GitHub side-effect
|
|
24
24
|
* (issue search, ticket creation) flows through INJECTED PORTS so the unit test
|
|
25
25
|
* runs with no network. Production wires the ports to the GitHub provider /
|
|
26
|
-
* `/
|
|
26
|
+
* `/plan` / `/plan` surfaces; tests pass in-memory stubs.
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
29
|
import { fingerprintFinding, routeFinding } from './route-finding.js';
|
|
@@ -40,8 +40,8 @@ export const PROMOTION_TARGETS = Object.freeze({
|
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
42
|
* A cluster of more than this many distinct coverage surfaces is broad enough
|
|
43
|
-
* to warrant an Epic (`/
|
|
44
|
-
* (`/
|
|
43
|
+
* to warrant an Epic (`/plan --idea`) rather than a single Story
|
|
44
|
+
* (`/plan`). One or two surfaces is a tight, single-deliverable cluster.
|
|
45
45
|
*/
|
|
46
46
|
const EPIC_COVERAGE_THRESHOLD = 2;
|
|
47
47
|
|
|
@@ -159,8 +159,8 @@ export function clusterLedgerItems(items) {
|
|
|
159
159
|
/**
|
|
160
160
|
* Decide a cluster's promotion target. A cluster that spans more than
|
|
161
161
|
* {@link EPIC_COVERAGE_THRESHOLD} distinct coverage surfaces is broad enough to
|
|
162
|
-
* warrant an Epic (`/
|
|
163
|
-
* Story (`/
|
|
162
|
+
* warrant an Epic (`/plan --idea`); otherwise it is a single-deliverable
|
|
163
|
+
* Story (`/plan`).
|
|
164
164
|
*
|
|
165
165
|
* @param {{ coverages: string[] }} cluster
|
|
166
166
|
* @returns {'story'|'epic'}
|
|
@@ -233,7 +233,7 @@ function routedToLink(issue, kind) {
|
|
|
233
233
|
* shared `routeFinding` against existing Issues (via the injected search
|
|
234
234
|
* port). This dedups against work already filed.
|
|
235
235
|
* 2. On a `new` decision, open the follow-up ticket through the injected
|
|
236
|
-
* `createStory` (`/
|
|
236
|
+
* `createStory` (`/plan`) or `createEpic` (`/plan --idea`) port,
|
|
237
237
|
* chosen by {@link targetForCluster}. On any other decision, link back to
|
|
238
238
|
* the matched Issue rather than creating a duplicate.
|
|
239
239
|
* 3. Stamp the resolved `routedTo` link onto every contributing ledger item
|
|
@@ -250,9 +250,9 @@ function routedToLink(issue, kind) {
|
|
|
250
250
|
* @param {(finding: object) => Promise<Array<{ number: number, state: string, title?: string, body?: string }>>} [ports.searchCandidates]
|
|
251
251
|
* Optional semantic candidate search, forwarded to `routeFinding`.
|
|
252
252
|
* @param {(cluster: object) => Promise<{ number: number, url?: string }>} ports.createStory
|
|
253
|
-
* Opens a single Story (`/
|
|
253
|
+
* Opens a single Story (`/plan`) for a tight cluster.
|
|
254
254
|
* @param {(cluster: object) => Promise<{ number: number, url?: string }>} ports.createEpic
|
|
255
|
-
* Opens an Epic (`/
|
|
255
|
+
* Opens an Epic (`/plan --idea`) for a broad cluster.
|
|
256
256
|
* @returns {Promise<{
|
|
257
257
|
* promotions: Array<{
|
|
258
258
|
* clusterKey: string,
|
|
@@ -76,7 +76,6 @@ export function isValidTransition(fromState, toState) {
|
|
|
76
76
|
|
|
77
77
|
export const TYPE_LABELS = {
|
|
78
78
|
EPIC: 'type::epic',
|
|
79
|
-
FEATURE: 'type::feature',
|
|
80
79
|
STORY: 'type::story',
|
|
81
80
|
};
|
|
82
81
|
|
|
@@ -104,7 +103,7 @@ export const CONTEXT_LABELS = {
|
|
|
104
103
|
export const CONTEXT_ACCEPTANCE_SPEC = CONTEXT_LABELS.ACCEPTANCE_SPEC;
|
|
105
104
|
|
|
106
105
|
/**
|
|
107
|
-
* Acceptance-axis labels for opt-out signalling on Stories
|
|
106
|
+
* Acceptance-axis labels for opt-out signalling on Stories that
|
|
108
107
|
* intentionally have no acceptance-spec coverage. Separate namespace from
|
|
109
108
|
* `context::` because it expresses absence rather than a linked context
|
|
110
109
|
* ticket.
|
|
@@ -120,7 +119,7 @@ export const ACCEPTANCE_NA = ACCEPTANCE_LABELS.N_A;
|
|
|
120
119
|
* loop). `meta::framework-gap` is applied to issues that surface a defect or
|
|
121
120
|
* missing capability in the framework itself; `meta::consumer-improvement`
|
|
122
121
|
* is applied to issues that surface improvements to a consumer project
|
|
123
|
-
* (workflow tweaks, ergonomic asks, doc polish). The `/
|
|
122
|
+
* (workflow tweaks, ergonomic asks, doc polish). The `/plan` Phase 0
|
|
124
123
|
* fetcher (see `lib/feedback-loop/prior-feedback-fetcher.js`) reads open
|
|
125
124
|
* issues carrying either label and surfaces them to the planner so retro
|
|
126
125
|
* signals are routed into durable substrates rather than lost in chat.
|
|
@@ -133,7 +132,7 @@ export const META_LABELS = {
|
|
|
133
132
|
/**
|
|
134
133
|
* Planning-axis labels (Epic #2880 F7). Currently scoped to the
|
|
135
134
|
* `planning::healthcheck-waived` operator-applied waiver, which is the
|
|
136
|
-
* documented escape hatch for the `/
|
|
135
|
+
* documented escape hatch for the `/plan` Phase 10 readiness
|
|
137
136
|
* healthcheck (`epic-plan-healthcheck.js`). The persist half of
|
|
138
137
|
* `epic-plan-decompose.js` refuses to flip an Epic to `agent::ready`
|
|
139
138
|
* when the healthcheck returned `ok: false` unless this label is
|
|
@@ -53,15 +53,10 @@ export const LABEL_TAXONOMY = [
|
|
|
53
53
|
color: LABEL_COLORS.TYPE,
|
|
54
54
|
description: 'Epic-level work item',
|
|
55
55
|
},
|
|
56
|
-
{
|
|
57
|
-
name: TYPE_LABELS.FEATURE,
|
|
58
|
-
color: LABEL_COLORS.TYPE,
|
|
59
|
-
description: 'Feature under an Epic',
|
|
60
|
-
},
|
|
61
56
|
{
|
|
62
57
|
name: TYPE_LABELS.STORY,
|
|
63
58
|
color: LABEL_COLORS.TYPE,
|
|
64
|
-
description: 'User story under
|
|
59
|
+
description: 'User story under an Epic',
|
|
65
60
|
},
|
|
66
61
|
|
|
67
62
|
// Agent State
|
|
@@ -75,7 +70,7 @@ export const LABEL_TAXONOMY = [
|
|
|
75
70
|
name: AGENT_LABELS.READY,
|
|
76
71
|
color: LABEL_COLORS.AGENT,
|
|
77
72
|
description:
|
|
78
|
-
'Parking state — frozen dispatch manifest exists; awaiting local /
|
|
73
|
+
'Parking state — frozen dispatch manifest exists; awaiting local /deliver',
|
|
79
74
|
},
|
|
80
75
|
{
|
|
81
76
|
name: AGENT_LABELS.EXECUTING,
|
|
@@ -120,7 +115,7 @@ export const LABEL_TAXONOMY = [
|
|
|
120
115
|
description: 'Acceptance Specification (Gherkin scenarios)',
|
|
121
116
|
},
|
|
122
117
|
|
|
123
|
-
// Acceptance axis — explicit opt-out signal for Stories
|
|
118
|
+
// Acceptance axis — explicit opt-out signal for Stories that
|
|
124
119
|
// intentionally have no acceptance-spec coverage.
|
|
125
120
|
{
|
|
126
121
|
name: ACCEPTANCE_LABELS.N_A,
|
|
@@ -169,8 +164,8 @@ export const STATUS_FIELD_OPTIONS = ['Todo', 'In Progress', 'Done'];
|
|
|
169
164
|
*
|
|
170
165
|
* GitHub's GraphQL surface does not expose a public `createProjectV2View`
|
|
171
166
|
* mutation, so bootstrap creates these via the REST Projects V2 views
|
|
172
|
-
* endpoint best-effort
|
|
173
|
-
*
|
|
167
|
+
* endpoint best-effort; when the endpoint is unavailable the views must be
|
|
168
|
+
* configured manually in the GitHub Projects UI.
|
|
174
169
|
*
|
|
175
170
|
* @type {Array<{ name: string, filter: string, groupBy: string,
|
|
176
171
|
* layout?: 'table'|'board'|'roadmap' }>}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* detect-stack.js — Consumer stack detection for
|
|
2
|
+
* detect-stack.js — Consumer stack detection for `mandrel init`
|
|
3
3
|
*
|
|
4
4
|
* Inspects a consumer repository root and reports the package manager,
|
|
5
5
|
* test runner, and primary language it can infer from on-disk signals
|
|
6
6
|
* (lockfiles, `package.json` contents, and source-file extensions). The
|
|
7
|
-
*
|
|
8
|
-
* what it found before scaffolding missing
|
|
7
|
+
* `mandrel init` configure-path tail (Feature #3514, Story #4045) uses
|
|
8
|
+
* this to tell the operator what it found before scaffolding missing
|
|
9
|
+
* `docsContextFiles`.
|
|
9
10
|
*
|
|
10
11
|
* The detection functions are seam-injectable: each takes an injected
|
|
11
12
|
* filesystem facade (`exists` / `readFile` / `listExtensions`) so they
|
|
@@ -19,6 +20,7 @@
|
|
|
19
20
|
|
|
20
21
|
import fs from 'node:fs';
|
|
21
22
|
import path from 'node:path';
|
|
23
|
+
import { detectPackageManager as detectPm } from '../detect-package-manager.js';
|
|
22
24
|
|
|
23
25
|
/**
|
|
24
26
|
* Filesystem facade. Pure detection logic talks to disk only through
|
|
@@ -163,18 +165,16 @@ export const defaultFsFacade = {
|
|
|
163
165
|
* when no lockfile is found but a `package.json` exists, and `null` when
|
|
164
166
|
* the repo has no Node manifest at all.
|
|
165
167
|
*
|
|
168
|
+
* Delegates to the shared `detectPackageManager` helper
|
|
169
|
+
* (Story #4048 B3 — one implementation per concept). The `fsFacade.exists`
|
|
170
|
+
* seam is forwarded directly.
|
|
171
|
+
*
|
|
166
172
|
* @param {string} root - Repository root.
|
|
167
173
|
* @param {FsFacade} [fsFacade=defaultFsFacade]
|
|
168
174
|
* @returns {'pnpm'|'yarn'|'bun'|'npm'|null}
|
|
169
175
|
*/
|
|
170
176
|
export function detectPackageManager(root, fsFacade = defaultFsFacade) {
|
|
171
|
-
|
|
172
|
-
if (exists(path.join(root, 'pnpm-lock.yaml'))) return 'pnpm';
|
|
173
|
-
if (exists(path.join(root, 'yarn.lock'))) return 'yarn';
|
|
174
|
-
if (exists(path.join(root, 'bun.lockb'))) return 'bun';
|
|
175
|
-
if (exists(path.join(root, 'package-lock.json'))) return 'npm';
|
|
176
|
-
if (exists(path.join(root, 'package.json'))) return 'npm';
|
|
177
|
-
return null;
|
|
177
|
+
return detectPm(root, fsFacade.exists);
|
|
178
178
|
}
|
|
179
179
|
|
|
180
180
|
/**
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* init-tail.js — post-bootstrap onboarding tail for `mandrel init`.
|
|
3
|
+
*
|
|
4
|
+
* Called by `mandrel init` after `bootstrap.js` completes successfully on
|
|
5
|
+
* the "configure now" path. Sequences the four phases that walk an operator
|
|
6
|
+
* from a freshly bootstrapped project to a ready-to-plan workspace:
|
|
7
|
+
*
|
|
8
|
+
* Phase 1 — Detect the consumer stack (lib/onboard/detect-stack.js).
|
|
9
|
+
* Phase 2 — Offer to scaffold missing docsContextFiles (scaffold-docs.js).
|
|
10
|
+
* Phase 3 — Run `mandrel doctor` as a readiness gate.
|
|
11
|
+
* Phase 4 — Print the /plan handoff next-step text.
|
|
12
|
+
*
|
|
13
|
+
* The whole tail is idempotent: re-running after an already-onboarded project
|
|
14
|
+
* re-detects, re-checks, and re-offers scaffolding without duplicating stubs
|
|
15
|
+
* (the scaffolder only writes genuinely absent files) and without modifying
|
|
16
|
+
* anything (doctor is read-only).
|
|
17
|
+
*
|
|
18
|
+
* Injectable seams: `runDoctor`, `stdout`, `confirmScaffold`, and `isTTY`
|
|
19
|
+
* allow the unit suite to drive every branch without real I/O.
|
|
20
|
+
*
|
|
21
|
+
* Story #4045 (refs #4045).
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { spawnSync as defaultSpawnSync } from 'node:child_process';
|
|
25
|
+
import fs from 'node:fs';
|
|
26
|
+
import path from 'node:path';
|
|
27
|
+
|
|
28
|
+
import { detectStack } from './detect-stack.js';
|
|
29
|
+
import { STUB_MARKER, scaffoldDocs } from './scaffold-docs.js';
|
|
30
|
+
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// Phase text constants
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Text printed at the end of the init tail to hand the operator off to /plan.
|
|
37
|
+
*
|
|
38
|
+
* @type {string}
|
|
39
|
+
*/
|
|
40
|
+
export const PLAN_HANDOFF_TEXT =
|
|
41
|
+
'\n✅ Mandrel is ready. Start your first Epic:\n\n' +
|
|
42
|
+
' /plan --idea "<one-line description of what you want to build>"\n\n' +
|
|
43
|
+
'Or, if you already have a `type::epic` Issue open:\n\n' +
|
|
44
|
+
' /plan <epicId>\n';
|
|
45
|
+
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
// Internal helpers
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Format a stack-detection result as a human-readable report line.
|
|
52
|
+
*
|
|
53
|
+
* @param {{ packageManager: string|null, testRunner: string|null, primaryLanguage: string|null }} stack
|
|
54
|
+
* @returns {string}
|
|
55
|
+
*/
|
|
56
|
+
function formatStackReport(stack) {
|
|
57
|
+
const pm = stack.packageManager ?? '(unknown)';
|
|
58
|
+
const runner = stack.testRunner ?? '(unknown)';
|
|
59
|
+
const lang = stack.primaryLanguage ?? '(unknown)';
|
|
60
|
+
return (
|
|
61
|
+
'\n[init] Stack detection:\n' +
|
|
62
|
+
` Package manager : ${pm}\n` +
|
|
63
|
+
` Test runner : ${runner}\n` +
|
|
64
|
+
` Primary language: ${lang}\n`
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Format a list of missing docs as a human-readable report (no prompt).
|
|
70
|
+
*
|
|
71
|
+
* @param {string[]} missing
|
|
72
|
+
* @returns {string}
|
|
73
|
+
*/
|
|
74
|
+
function formatMissingList(missing) {
|
|
75
|
+
if (missing.length === 0) return '';
|
|
76
|
+
const list = missing.map((f) => ` • ${f}`).join('\n');
|
|
77
|
+
return (
|
|
78
|
+
'\n[init] The following docsContextFiles are missing — agents load\n' +
|
|
79
|
+
'these before every task:\n' +
|
|
80
|
+
`${list}\n`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Prompt text shown only on a TTY when asking to scaffold. */
|
|
85
|
+
const SCAFFOLD_PROMPT = '\nScaffold stubs now? [y/N]: ';
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Synchronous y/N read from stdin (fd 0). Returns `false` on any read error
|
|
89
|
+
* or when the user enters something other than `y` / `yes`.
|
|
90
|
+
*
|
|
91
|
+
* @returns {boolean}
|
|
92
|
+
*/
|
|
93
|
+
function syncConfirm() {
|
|
94
|
+
let answer = '';
|
|
95
|
+
try {
|
|
96
|
+
const buf = fs.readFileSync(0, 'utf8');
|
|
97
|
+
answer = buf.split('\n', 1)[0].trim().toLowerCase();
|
|
98
|
+
} catch {
|
|
99
|
+
answer = '';
|
|
100
|
+
}
|
|
101
|
+
return answer === 'y' || answer === 'yes';
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
// Public API
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Run the post-bootstrap init tail.
|
|
110
|
+
*
|
|
111
|
+
* @param {object} [opts]
|
|
112
|
+
* @param {string} [opts.root] - Project root (defaults to `process.cwd()`).
|
|
113
|
+
* @param {(msg: string) => void} [opts.stdout] - Output sink (defaults to
|
|
114
|
+
* `process.stdout.write` bound to `process.stdout`).
|
|
115
|
+
* @param {() => boolean} [opts.confirmScaffold] - Read the operator's y/N
|
|
116
|
+
* answer for the scaffold offer. Returns `true` to scaffold. In non-TTY
|
|
117
|
+
* contexts defaults to `false` (no write without operator confirmation).
|
|
118
|
+
* @param {(extraArgs?: string[]) => { status: number|null }} [opts.runDoctor]
|
|
119
|
+
* - Run `mandrel doctor`; injectable for tests.
|
|
120
|
+
* @param {boolean} [opts.isTTY] - Whether stdin is a TTY (defaults to
|
|
121
|
+
* `Boolean(process.stdin.isTTY)`).
|
|
122
|
+
* @returns {{
|
|
123
|
+
* stack: { packageManager: string|null, testRunner: string|null, primaryLanguage: string|null },
|
|
124
|
+
* scaffoldResult: object,
|
|
125
|
+
* doctorStatus: number,
|
|
126
|
+
* ok: boolean,
|
|
127
|
+
* }}
|
|
128
|
+
*/
|
|
129
|
+
export function runInitTail({
|
|
130
|
+
root,
|
|
131
|
+
stdout = (s) => process.stdout.write(s),
|
|
132
|
+
confirmScaffold,
|
|
133
|
+
runDoctor,
|
|
134
|
+
isTTY,
|
|
135
|
+
} = {}) {
|
|
136
|
+
const projectRoot = root ?? process.cwd();
|
|
137
|
+
const tty = isTTY ?? Boolean(process.stdin.isTTY);
|
|
138
|
+
|
|
139
|
+
// When confirmScaffold is explicitly injected (e.g. from tests), always use
|
|
140
|
+
// it. When using the default, auto-decline on non-TTY so the scaffolder
|
|
141
|
+
// never writes unattended.
|
|
142
|
+
const usingDefaultConfirm = confirmScaffold == null;
|
|
143
|
+
const confirmFn = confirmScaffold ?? (() => syncConfirm());
|
|
144
|
+
|
|
145
|
+
// Default doctor runner — spawns `mandrel doctor` via the locally installed
|
|
146
|
+
// bin; inherits stdio so the report streams to the terminal.
|
|
147
|
+
const mandrelBin = path.join(
|
|
148
|
+
projectRoot,
|
|
149
|
+
'node_modules',
|
|
150
|
+
'mandrel',
|
|
151
|
+
'bin',
|
|
152
|
+
'mandrel.js',
|
|
153
|
+
);
|
|
154
|
+
const defaultRunDoctor = (extraArgs = []) =>
|
|
155
|
+
defaultSpawnSync(process.execPath, [mandrelBin, 'doctor', ...extraArgs], {
|
|
156
|
+
cwd: projectRoot,
|
|
157
|
+
stdio: 'inherit',
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
const doctorFn = runDoctor ?? defaultRunDoctor;
|
|
161
|
+
|
|
162
|
+
// --- Phase 1: Detect the stack -------------------------------------------
|
|
163
|
+
let stack;
|
|
164
|
+
try {
|
|
165
|
+
stack = detectStack(projectRoot);
|
|
166
|
+
} catch {
|
|
167
|
+
stack = { packageManager: null, testRunner: null, primaryLanguage: null };
|
|
168
|
+
}
|
|
169
|
+
stdout(formatStackReport(stack));
|
|
170
|
+
|
|
171
|
+
// --- Phase 2: Offer to scaffold missing docsContextFiles -----------------
|
|
172
|
+
const preview = scaffoldDocs({ root: projectRoot, write: false });
|
|
173
|
+
let scaffoldResult = preview;
|
|
174
|
+
|
|
175
|
+
if (preview.missing.length === 0) {
|
|
176
|
+
stdout('\n[init] All docsContextFiles are present.\n');
|
|
177
|
+
} else {
|
|
178
|
+
stdout(formatMissingList(preview.missing));
|
|
179
|
+
// On non-TTY without an injected confirm, auto-decline so the scaffolder
|
|
180
|
+
// never writes unattended. On TTY (or with an injected confirm seam), show
|
|
181
|
+
// the prompt and consult the confirm function.
|
|
182
|
+
const canPrompt = tty || !usingDefaultConfirm;
|
|
183
|
+
if (canPrompt) stdout(SCAFFOLD_PROMPT);
|
|
184
|
+
const accepted = canPrompt ? confirmFn() : false;
|
|
185
|
+
if (accepted) {
|
|
186
|
+
scaffoldResult = scaffoldDocs({ root: projectRoot, write: true });
|
|
187
|
+
if (scaffoldResult.created.length > 0) {
|
|
188
|
+
stdout(
|
|
189
|
+
`[init] Scaffolded ${scaffoldResult.created.length} stub(s). ` +
|
|
190
|
+
`Each carries a \`${STUB_MARKER}\` marker — replace placeholder ` +
|
|
191
|
+
'content before planning.\n',
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
} else {
|
|
195
|
+
stdout(
|
|
196
|
+
'[init] Scaffolding declined. docsContextFiles are still missing — ' +
|
|
197
|
+
'agents will load degraded context until you create them.\n',
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// --- Phase 3: Readiness gate (mandrel doctor) ----------------------------
|
|
203
|
+
stdout('\n[init] Running mandrel doctor…\n');
|
|
204
|
+
const doctorResult = doctorFn();
|
|
205
|
+
const doctorStatus = doctorResult?.status ?? 1;
|
|
206
|
+
|
|
207
|
+
if (doctorStatus !== 0) {
|
|
208
|
+
stdout(
|
|
209
|
+
'\n[init] ❌ Doctor check failed. Resolve the remedies above and\n' +
|
|
210
|
+
'then re-run: mandrel init\n',
|
|
211
|
+
);
|
|
212
|
+
return { stack, scaffoldResult, doctorStatus, ok: false };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// --- Phase 4: Handoff to /plan -------------------------------------------
|
|
216
|
+
stdout(PLAN_HANDOFF_TEXT);
|
|
217
|
+
return { stack, scaffoldResult, doctorStatus, ok: true };
|
|
218
|
+
}
|
|
@@ -24,6 +24,15 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
24
24
|
const AGENT_ROOT = path.resolve(__dirname, '../../..');
|
|
25
25
|
const DOCS_TEMPLATE_DIR = path.join(AGENT_ROOT, 'templates', 'docs');
|
|
26
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Deterministic marker written into every scaffolded stub. The `/plan`
|
|
29
|
+
* first-run preflight (and any tooling that wants to detect unedited stubs)
|
|
30
|
+
* keys off this exact string — do not change it without a hard cutover.
|
|
31
|
+
*
|
|
32
|
+
* @type {string}
|
|
33
|
+
*/
|
|
34
|
+
export const STUB_MARKER = '<!-- MANDREL:STUB -->';
|
|
35
|
+
|
|
27
36
|
/**
|
|
28
37
|
* Build the generic placeholder stub for a docsContextFile that has no
|
|
29
38
|
* dedicated template. Derives a human-readable title from the filename.
|
|
@@ -39,8 +48,9 @@ function genericStub(fileName) {
|
|
|
39
48
|
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
40
49
|
.join(' ');
|
|
41
50
|
return (
|
|
51
|
+
`${STUB_MARKER}\n` +
|
|
42
52
|
`# ${title}\n\n` +
|
|
43
|
-
'> **Stub generated by
|
|
53
|
+
'> **Stub generated by `mandrel init`.** This file is one of the\n' +
|
|
44
54
|
'> `project.docsContextFiles` mandatory-reads — agents load it before\n' +
|
|
45
55
|
'> every task. Replace this placeholder with real content.\n'
|
|
46
56
|
);
|
|
@@ -48,7 +58,9 @@ function genericStub(fileName) {
|
|
|
48
58
|
|
|
49
59
|
/**
|
|
50
60
|
* Read the dedicated template body for a docsContextFile, or fall back to the
|
|
51
|
-
* generic stub when no template ships for that name.
|
|
61
|
+
* generic stub when no template ships for that name. Either path prepends the
|
|
62
|
+
* {@link STUB_MARKER} so the `/plan` first-run preflight can detect un-edited
|
|
63
|
+
* stubs regardless of whether a dedicated template was used.
|
|
52
64
|
*
|
|
53
65
|
* @param {string} fileName
|
|
54
66
|
* @param {import('node:fs')} fsImpl
|
|
@@ -57,7 +69,10 @@ function genericStub(fileName) {
|
|
|
57
69
|
function stubContentFor(fileName, fsImpl) {
|
|
58
70
|
const templatePath = path.join(DOCS_TEMPLATE_DIR, fileName);
|
|
59
71
|
if (fsImpl.existsSync(templatePath)) {
|
|
60
|
-
|
|
72
|
+
const body = fsImpl.readFileSync(templatePath, 'utf8');
|
|
73
|
+
// Prepend the marker only when absent (idempotent; the template files
|
|
74
|
+
// themselves are kept marker-free so they read cleanly as documentation).
|
|
75
|
+
return body.startsWith(STUB_MARKER) ? body : `${STUB_MARKER}\n${body}`;
|
|
61
76
|
}
|
|
62
77
|
return genericStub(fileName);
|
|
63
78
|
}
|
|
@@ -152,7 +152,7 @@ export function decideAcceptanceEval({ verdict, maxRounds, round: roundIn }) {
|
|
|
152
152
|
/**
|
|
153
153
|
* Build the per-criterion acceptance-eval signal payload for the retro /
|
|
154
154
|
* feedback substrate. Carries which acceptance items needed rework and the
|
|
155
|
-
* round count so `/
|
|
155
|
+
* round count so `/plan` Phase 0 feedback fetch and the retro can
|
|
156
156
|
* surface acceptance churn. PII-free by construction — it carries only
|
|
157
157
|
* acceptance-item indices, verdicts, and the terminal decision.
|
|
158
158
|
*
|