mandrel 1.93.0 → 1.94.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/agents/acceptance-critic.md +129 -0
- package/.agents/agents/retro.md +42 -0
- package/.agents/agents/story-worker.md +162 -0
- package/.agents/docs/configuration.md +7 -1
- package/.agents/docs/execution-reference.md +27 -2
- package/.agents/instructions.md +43 -33
- package/.agents/personas/engineer.md +26 -112
- package/.agents/personas/security-engineer.md +1 -2
- package/.agents/rules/git-conventions-reference.md +225 -0
- package/.agents/rules/git-conventions.md +25 -200
- package/.agents/rules/security-baseline.md +5 -0
- package/.agents/rules/testing-standards.md +106 -13
- package/.agents/schemas/agentrc.schema.json +31 -1
- package/.agents/schemas/lifecycle/slice.end.schema.json +21 -0
- package/.agents/schemas/lifecycle/slice.heartbeat.schema.json +20 -0
- package/.agents/schemas/lifecycle/slice.start.schema.json +17 -0
- package/.agents/scripts/acceptance-eval.js +62 -18
- package/.agents/scripts/agents-bootstrap-github.js +1 -1
- package/.agents/scripts/bookkeeping-reconcile.js +117 -0
- package/.agents/scripts/check-context-budget.js +62 -5
- package/.agents/scripts/diagnose-friction.js +0 -6
- package/.agents/scripts/epic-deliver-prepare.js +272 -10
- package/.agents/scripts/lib/bootstrap/project-bootstrap.js +56 -18
- package/.agents/scripts/lib/close-validation/gates.js +159 -21
- package/.agents/scripts/lib/config/acceptance-eval.js +52 -5
- package/.agents/scripts/lib/config/delivery-routing.js +87 -0
- package/.agents/scripts/lib/config/explain.js +2 -0
- package/.agents/scripts/lib/config-resolver.js +1 -1
- package/.agents/scripts/lib/config-settings-schema-delivery.js +37 -3
- package/.agents/scripts/lib/config-settings-schema-quality.js +9 -0
- package/.agents/scripts/lib/doc-tiers.js +37 -2
- package/.agents/scripts/lib/observability/active-story-env.js +111 -2
- package/.agents/scripts/lib/observability/hook-heartbeat.js +219 -0
- package/.agents/scripts/lib/observability/tool-trace-hook.js +15 -4
- package/.agents/scripts/lib/orchestration/acceptance-clusters.js +111 -0
- package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +32 -4
- package/.agents/scripts/lib/orchestration/bookkeeping-outbox.js +270 -0
- package/.agents/scripts/lib/orchestration/ceremony-routing.js +141 -0
- package/.agents/scripts/lib/orchestration/context-hydration-engine.js +3 -124
- package/.agents/scripts/lib/orchestration/deliver-route.js +173 -0
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/authoring-context.js +1 -1
- package/.agents/scripts/lib/orchestration/epic-run-state-store.js +233 -0
- package/.agents/scripts/lib/orchestration/lifecycle/emit-slice-lifecycle.js +270 -0
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/acceptance-reconciler.js +83 -2
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/checkpoint-pointer-writer.js +6 -0
- package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +3 -2
- package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +1 -0
- package/.agents/scripts/lib/orchestration/story-close/pre-merge-validation.js +1 -0
- package/.agents/scripts/lib/orchestration/ticket-validator.js +1 -1
- package/.agents/scripts/lib/provider-factory.js +1 -1
- package/.agents/scripts/lib/templates/decomposer-prompts.js +1 -1
- package/.agents/scripts/post-structured-comment.js +38 -0
- package/.agents/scripts/slice-phase.js +361 -0
- package/.agents/scripts/sync-claude-agents.js +165 -0
- package/.agents/scripts/update-ticket-state.js +31 -0
- package/.agents/scripts/wave-tick.js +138 -9
- package/.agents/skills/core/api-and-interface-design/SKILL.md +5 -3
- package/.agents/skills/core/code-review-and-quality/SKILL.md +63 -7
- package/.agents/skills/core/debugging-and-error-recovery/SKILL.md +1 -1
- package/.agents/skills/core/epic-plan-consolidate/SKILL.md +5 -5
- package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +8 -8
- package/.agents/skills/core/epic-plan-premortem/SKILL.md +4 -4
- package/.agents/skills/core/epic-plan-spec-author/SKILL.md +26 -56
- package/.agents/skills/core/gates-and-baselines/SKILL.md +149 -0
- package/.agents/skills/core/idea-refinement/SKILL.md +2 -8
- package/.agents/skills/core/qa-coverage-mapping/SKILL.md +7 -7
- package/.agents/skills/skills.index.json +11 -381
- package/.agents/workflows/deliver.md +47 -4
- package/.agents/workflows/helpers/acceptance-self-eval.md +38 -13
- package/.agents/workflows/helpers/deliver-epic-reference.md +18 -5
- package/.agents/workflows/helpers/deliver-epic-single.md +331 -0
- package/.agents/workflows/helpers/deliver-epic.md +51 -8
- package/.agents/workflows/helpers/deliver-stories.md +15 -5
- package/.agents/workflows/helpers/epic-deliver-story.md +12 -3
- package/.agents/workflows/helpers/mandrel-sync-config.md +1 -1
- package/.agents/workflows/helpers/plan-epic.md +25 -23
- package/.agents/workflows/mandrel-update.md +1 -1
- package/docs/CHANGELOG.md +16 -0
- package/lib/cli/registry.js +95 -0
- package/package.json +4 -2
- package/.agents/personas/engineer-mobile.md +0 -120
- package/.agents/personas/engineer-web.md +0 -111
- package/.agents/personas/product.md +0 -94
- package/.agents/personas/refactorer.md +0 -113
- package/.agents/personas/sre.md +0 -86
- package/.agents/personas/ux-designer.md +0 -95
- package/.agents/scripts/epic-plan-decompose.js +0 -54
- package/.agents/scripts/epic-plan-spec.js +0 -64
- package/.agents/scripts/lib/orchestration/skill-capsule-loader.js +0 -109
- package/.agents/scripts/plan-critics.js +0 -199
- package/.agents/skills/core/baseline-refresh/SKILL.md +0 -181
- package/.agents/skills/core/ci-cd-and-automation/SKILL.md +0 -274
- package/.agents/skills/core/ci-cd-and-automation/examples.md +0 -211
- package/.agents/skills/core/code-simplification/SKILL.md +0 -389
- package/.agents/skills/core/context-engineering/SKILL.md +0 -309
- package/.agents/skills/core/context-engineering/examples.md +0 -58
- package/.agents/skills/core/deprecation-and-migration/SKILL.md +0 -250
- package/.agents/skills/core/frontend-ui-engineering/SKILL.md +0 -357
- package/.agents/skills/core/hydrate-context/SKILL.md +0 -123
- package/.agents/skills/core/idea-refinement/examples.md +0 -437
- package/.agents/skills/core/idea-refinement/frameworks.md +0 -135
- package/.agents/skills/core/incremental-implementation/SKILL.md +0 -271
- package/.agents/skills/core/introducing-a-baseline-gate/SKILL.md +0 -213
- package/.agents/skills/core/knowledge-transfer/SKILL.md +0 -180
- package/.agents/skills/core/mutation-survivor-remediation/SKILL.md +0 -117
- package/.agents/skills/core/performance-optimization/SKILL.md +0 -314
- package/.agents/skills/core/planning-and-task-breakdown/SKILL.md +0 -277
- package/.agents/skills/core/property-based-testing/SKILL.md +0 -148
- package/.agents/skills/core/refactoring-discipline/SKILL.md +0 -111
- package/.agents/skills/core/shipping-and-launch/SKILL.md +0 -328
- package/.agents/skills/core/spec-driven-development/SKILL.md +0 -252
- package/.agents/skills/core/test-driven-development/SKILL.md +0 -475
- package/.agents/skills/core/using-agent-skills/SKILL.md +0 -232
- package/.agents/skills/stack/architecture/monorepo-path-strategist/SKILL.md +0 -31
- package/.agents/skills/stack/architecture/structured-output-zod/SKILL.md +0 -51
- package/.agents/skills/stack/architecture/subagent-orchestration/SKILL.md +0 -76
- package/.agents/skills/stack/backend/cloudflare-hono-architect/SKILL.md +0 -31
- package/.agents/skills/stack/backend/cloudflare-hono-architect/examples/route-template.ts +0 -33
- package/.agents/skills/stack/backend/cloudflare-queue-manager/SKILL.md +0 -31
- package/.agents/skills/stack/backend/cloudflare-workers/SKILL.md +0 -51
- package/.agents/skills/stack/backend/highlevel-crm/SKILL.md +0 -54
- package/.agents/skills/stack/backend/sqlite-drizzle-expert/SKILL.md +0 -29
- package/.agents/skills/stack/backend/sqlite-drizzle-expert/examples/schema-template.ts +0 -30
- package/.agents/skills/stack/backend/stripe-integration/SKILL.md +0 -57
- package/.agents/skills/stack/backend/stripe-integration/scripts/listen-stripe.sh +0 -9
- package/.agents/skills/stack/backend/turso-sqlite/SKILL.md +0 -48
- package/.agents/skills/stack/frontend/astro/SKILL.md +0 -62
- package/.agents/skills/stack/frontend/astro-react-island-strategist/SKILL.md +0 -30
- package/.agents/skills/stack/frontend/expo-react-native-developer/SKILL.md +0 -29
- package/.agents/skills/stack/frontend/google-analytics-v4/SKILL.md +0 -50
- package/.agents/skills/stack/frontend/tailwind-v4/SKILL.md +0 -58
- package/.agents/skills/stack/frontend/ui-accessibility-engineer/SKILL.md +0 -34
- package/.agents/skills/stack/qa/audit-accessibility/SKILL.md +0 -51
- package/.agents/skills/stack/qa/lighthouse-baseline/SKILL.md +0 -199
- package/.agents/skills/stack/security/backend-security-patterns/SKILL.md +0 -68
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
# Role: UX/UI Designer
|
|
2
|
-
|
|
3
|
-
## 1. Primary Objective
|
|
4
|
-
|
|
5
|
-
You are the empathetic advocate for the end user. Your goal is to design
|
|
6
|
-
intuitive, low-friction, and accessible interfaces. You focus heavily on
|
|
7
|
-
**cognitive load**, **micro-interactions**, **edge cases**, and
|
|
8
|
-
**accessibility** before a single line of frontend code is written.
|
|
9
|
-
|
|
10
|
-
**Golden Rule:** The "happy path" is only 10% of the user experience. You define
|
|
11
|
-
what happens when data fails to load, when the user has no items, or when an
|
|
12
|
-
action is destructive.
|
|
13
|
-
|
|
14
|
-
> **Note:** For defining business value, MVP scoping, and PRDs, defer to the
|
|
15
|
-
> `product.md` persona.
|
|
16
|
-
|
|
17
|
-
## 2. Interaction Protocol
|
|
18
|
-
|
|
19
|
-
1. **Contextualize the User:** Understand the Epic body and its user stories.
|
|
20
|
-
Identify the primary Call to Action (CTA).
|
|
21
|
-
2. **Flow Before UI:** Do not design specific UI components until the entire
|
|
22
|
-
end-to-end user flow is mapped out and theoretically sound.
|
|
23
|
-
3. **State Management:** Define every state of a page or component (Empty,
|
|
24
|
-
Loading, Error, Ideal, Partial).
|
|
25
|
-
4. **Delegate:** Provide clear specifications (flows, states, accessibility
|
|
26
|
-
rules) for the Web and Mobile Engineers to implement.
|
|
27
|
-
|
|
28
|
-
## 3. Core Responsibilities
|
|
29
|
-
|
|
30
|
-
### A. User Experience (UX) & Flow
|
|
31
|
-
|
|
32
|
-
- **Journey Mapping:** Visualize complex user journeys using MermaidJS
|
|
33
|
-
flowcharts.
|
|
34
|
-
- **Friction Reduction:** Identify steps where a user might drop off or get
|
|
35
|
-
confused, and design mitigations.
|
|
36
|
-
- **Edge Cases & Error States:** Explicitly define 404 pages, empty states,
|
|
37
|
-
skeleton loaders, and validation error messages. Ensure error messages are
|
|
38
|
-
actionable, not just technical jargon.
|
|
39
|
-
|
|
40
|
-
### B. Visual Hierarchy & UI Patterns
|
|
41
|
-
|
|
42
|
-
- **Mobile First:** Always specify how a feature behaves on mobile or smaller
|
|
43
|
-
viewports before scaling up to desktop patterns.
|
|
44
|
-
- **Component States:** Define hover, active, focus, disabled, and error styles
|
|
45
|
-
for all interactive elements to pass to frontend engineers.
|
|
46
|
-
- **Consistency:** If a `docs/style-guide.md` is provided, you MUST strictly
|
|
47
|
-
adhere to its design tokens (spacing, typography, colors), UI copywriting
|
|
48
|
-
rules, and contextual themes. Prevent the introduction of ad-hoc UI patterns.
|
|
49
|
-
- **Tailwind v4 Guardrail:** Adhere to CSS-first styling. Do not propose
|
|
50
|
-
configuration changes to legacy `tailwind.config.js` or `tailwind.config.ts`.
|
|
51
|
-
Focus on the `@theme` directive.
|
|
52
|
-
|
|
53
|
-
### C. Accessibility (UX Definition)
|
|
54
|
-
|
|
55
|
-
- **WCAG 2.1 AA Checklist:** Define the accessibility _requirements_ for
|
|
56
|
-
specific features (e.g., "This modal must trap focus and close on `ESC`").
|
|
57
|
-
- **Contrast & Color Blindness:** Ensure critical information is not conveyed by
|
|
58
|
-
color alone.
|
|
59
|
-
- **Screen Reader Context:** Specify `aria-labels` and hidden text required to
|
|
60
|
-
make complex visual components understandable to screen readers.
|
|
61
|
-
|
|
62
|
-
> **Ownership Note:** This persona defines the _requirements_ for accessibility.
|
|
63
|
-
> Web/Mobile Engineers implement them, and SRE/DevOps enforces them in CI/CD.
|
|
64
|
-
|
|
65
|
-
## 4. Output Artifacts
|
|
66
|
-
|
|
67
|
-
### Level 1: Component Specification (Output to Chat)
|
|
68
|
-
|
|
69
|
-
- **States:** Detailed breakdown of Default, Hover, Active, Disabled, Error.
|
|
70
|
-
- **A11y Rules:** Specific keyboard navigation strings or ARIA requirements.
|
|
71
|
-
|
|
72
|
-
### Level 2: The User Flow (MermaidJS)
|
|
73
|
-
|
|
74
|
-
Use MermaidJS to visualize the journey **before** UI design or implementation
|
|
75
|
-
begins. Include decision nodes and error states mapped out visibly.
|
|
76
|
-
|
|
77
|
-
## 5. Scope Boundaries
|
|
78
|
-
|
|
79
|
-
**This persona does NOT:**
|
|
80
|
-
|
|
81
|
-
- Write implementation code, UI components, or CSS (use `engineer-web.md` or
|
|
82
|
-
`engineer-mobile.md`).
|
|
83
|
-
- Make business prioritization or MVP scoping decisions (use `product.md`).
|
|
84
|
-
- Design system architecture or write technical specifications.
|
|
85
|
-
- Execute tests, manage test data, or run CI/CD pipelines.
|
|
86
|
-
- Manage infrastructure, observability, or incident response.
|
|
87
|
-
|
|
88
|
-
**Automatic Referral Protocol:** If you are asked to perform a task that falls
|
|
89
|
-
outside the responsibilities defined in this file, **do not attempt it**.
|
|
90
|
-
Instead:
|
|
91
|
-
|
|
92
|
-
1. Briefly state which part of the request is outside your scope.
|
|
93
|
-
2. Read the `.agents/personas/` directory to identify the correct persona.
|
|
94
|
-
3. Automatically adopt that persona's instructions for the out-of-scope portion
|
|
95
|
-
of the work and continue execution seamlessly.
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/* node:coverage ignore file */
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* epic-plan-decompose.js — RETIRED delegate CLI (Epic #4474, PR7).
|
|
7
|
-
*
|
|
8
|
-
* The 12-phase plan pipeline collapsed to context → author → persist:
|
|
9
|
-
*
|
|
10
|
-
* - `--emit-context` moved to `plan-context.js` (the single authoring
|
|
11
|
-
* envelope carries the decomposer context — ticket schema, risk
|
|
12
|
-
* heuristics, ticket cap — alongside the spec half).
|
|
13
|
-
* - The persist half (ticket validator, file-assumption gate, DAG,
|
|
14
|
-
* budget, story creation, healthcheck, `agent::ready` flip) moved to
|
|
15
|
-
* `plan-persist.js` (single GitHub-write surface).
|
|
16
|
-
*
|
|
17
|
-
* This file is a **re-export shim only** — it carries external importers of
|
|
18
|
-
* the historic named-export surface one more release (#4474 design §6 PR7
|
|
19
|
-
* risk note) and is deleted in the next release. Internal consumers import
|
|
20
|
-
* the phase modules directly; do not add new imports of this file.
|
|
21
|
-
*
|
|
22
|
-
* Invoking it as a CLI is refused with a pointer to the successor CLIs.
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
// cli-opt-out: retired delegate shim (Epic #4474 PR7) — deliberately
|
|
26
|
-
// refuses CLI execution with a pointer to plan-context.js/plan-persist.js
|
|
27
|
-
// instead of wiring runAsCli around a dead main().
|
|
28
|
-
import { pathToFileURL } from 'node:url';
|
|
29
|
-
|
|
30
|
-
export {
|
|
31
|
-
buildDecomposerSystemPrompt,
|
|
32
|
-
buildDecompositionContext,
|
|
33
|
-
} from './lib/orchestration/epic-plan-decompose/phases/context.js';
|
|
34
|
-
export {
|
|
35
|
-
orderTicketsForCreation,
|
|
36
|
-
resolveDependencies,
|
|
37
|
-
} from './lib/orchestration/epic-plan-decompose/phases/dag.js';
|
|
38
|
-
export { runDecomposePhase } from './lib/orchestration/epic-plan-decompose/phases/persist.js';
|
|
39
|
-
|
|
40
|
-
// CLI execution is retired — fail loudly with the successor surface instead
|
|
41
|
-
// of silently doing nothing (a stale automation script should break visibly).
|
|
42
|
-
if (
|
|
43
|
-
process.argv[1] &&
|
|
44
|
-
import.meta.url === pathToFileURL(process.argv[1]).href
|
|
45
|
-
) {
|
|
46
|
-
process.stderr.write(
|
|
47
|
-
'[epic-plan-decompose] retired (Epic #4474): the plan pipeline is ' +
|
|
48
|
-
'context → author → persist.\n' +
|
|
49
|
-
' - authoring envelope: node .agents/scripts/plan-context.js --epic <id>\n' +
|
|
50
|
-
' - persist (all gates): node .agents/scripts/plan-persist.js --epic <id> --tickets ...\n' +
|
|
51
|
-
'This file survives one release as an import shim only.\n',
|
|
52
|
-
);
|
|
53
|
-
process.exit(1);
|
|
54
|
-
}
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/* node:coverage ignore file */
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* epic-plan-spec.js — RETIRED delegate CLI (Epic #4474, PR7).
|
|
7
|
-
*
|
|
8
|
-
* The 12-phase plan pipeline collapsed to context → author → persist:
|
|
9
|
-
*
|
|
10
|
-
* - `--emit-context` moved to `plan-context.js` (single authoring
|
|
11
|
-
* envelope: Epic body + docs digest + codebase snapshot + duplicate
|
|
12
|
-
* search + clarity + system prompts).
|
|
13
|
-
* - The persist half (section gate, risk verdict, managed sections,
|
|
14
|
-
* checkpoint) moved to `plan-persist.js` (single GitHub-write surface).
|
|
15
|
-
*
|
|
16
|
-
* This file is a **re-export shim only** — it carries external importers of
|
|
17
|
-
* the historic named-export surface one more release (#4474 design §6 PR7
|
|
18
|
-
* risk note) and is deleted in the next release. Internal consumers import
|
|
19
|
-
* the phase modules directly; do not add new imports of this file.
|
|
20
|
-
*
|
|
21
|
-
* Invoking it as a CLI is refused with a pointer to the successor CLIs.
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
// cli-opt-out: retired delegate shim (Epic #4474 PR7) — deliberately
|
|
25
|
-
// refuses CLI execution with a pointer to plan-context.js/plan-persist.js
|
|
26
|
-
// instead of wiring runAsCli around a dead main().
|
|
27
|
-
import { pathToFileURL } from 'node:url';
|
|
28
|
-
|
|
29
|
-
export {
|
|
30
|
-
forkAndCommitEpicSnapshot,
|
|
31
|
-
forkMainToEpic,
|
|
32
|
-
} from './lib/baseline-snapshot.js';
|
|
33
|
-
export {
|
|
34
|
-
buildAuthoringContext,
|
|
35
|
-
resolveMemoryDir,
|
|
36
|
-
} from './lib/orchestration/epic-plan-spec/phases/authoring-context.js';
|
|
37
|
-
export { drainPendingCleanupAtBoot } from './lib/orchestration/epic-plan-spec/phases/drain.js';
|
|
38
|
-
export {
|
|
39
|
-
planEpic,
|
|
40
|
-
resolveAcceptancePersistence,
|
|
41
|
-
} from './lib/orchestration/epic-plan-spec/phases/plan-epic.js';
|
|
42
|
-
export {
|
|
43
|
-
loadRiskVerdict,
|
|
44
|
-
validateRiskVerdict,
|
|
45
|
-
} from './lib/orchestration/epic-plan-spec/phases/risk-verdict.js';
|
|
46
|
-
export { runSpecPhase } from './lib/orchestration/epic-plan-spec/phases/run-spec-phase.js';
|
|
47
|
-
export { runSpecFreshnessCheck } from './lib/orchestration/epic-plan-spec/phases/spec-freshness.js';
|
|
48
|
-
export { resolveReviewRouting } from './lib/orchestration/plan-review-routing.js';
|
|
49
|
-
|
|
50
|
-
// CLI execution is retired — fail loudly with the successor surface instead
|
|
51
|
-
// of silently doing nothing (a stale automation script should break visibly).
|
|
52
|
-
if (
|
|
53
|
-
process.argv[1] &&
|
|
54
|
-
import.meta.url === pathToFileURL(process.argv[1]).href
|
|
55
|
-
) {
|
|
56
|
-
process.stderr.write(
|
|
57
|
-
'[epic-plan-spec] retired (Epic #4474): the plan pipeline is ' +
|
|
58
|
-
'context → author → persist.\n' +
|
|
59
|
-
' - authoring envelope: node .agents/scripts/plan-context.js --epic <id>\n' +
|
|
60
|
-
' - persist (all gates): node .agents/scripts/plan-persist.js --epic <id> ...\n' +
|
|
61
|
-
'This file survives one release as an import shim only.\n',
|
|
62
|
-
);
|
|
63
|
-
process.exit(1);
|
|
64
|
-
}
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* skill-capsule-loader.js — Policy Capsule extraction for skill hydration.
|
|
3
|
-
*
|
|
4
|
-
* Resolves skills via `skills.index.json` and returns the Policy Capsule
|
|
5
|
-
* region. When a SKILL.md is missing its capsule marker the full body is
|
|
6
|
-
* returned as a defensive fallback (the manifest is malformed); there is no
|
|
7
|
-
* caller-facing opt-in to inline full bodies — capsule-only is the contract
|
|
8
|
-
* (Story #3863, hard cutover).
|
|
9
|
-
*
|
|
10
|
-
* @module lib/orchestration/skill-capsule-loader
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import fs from 'node:fs';
|
|
14
|
-
import path from 'node:path';
|
|
15
|
-
import { Logger } from '../Logger.js';
|
|
16
|
-
import { PROJECT_ROOT } from '../project-root.js';
|
|
17
|
-
|
|
18
|
-
const POLICY_HEADING_RE = /^## Policy Capsule\s*$/;
|
|
19
|
-
const ANY_H2_RE = /^## /;
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Split source on CRLF or LF without normalizing line endings.
|
|
23
|
-
*
|
|
24
|
-
* @param {string} src
|
|
25
|
-
* @returns {string[]}
|
|
26
|
-
*/
|
|
27
|
-
function splitLines(src) {
|
|
28
|
-
return src.split(/\r\n|\n/);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Extract the Policy Capsule region from a SKILL.md body (heading through
|
|
33
|
-
* the line before the next `## ` heading). Returns null when absent.
|
|
34
|
-
*
|
|
35
|
-
* @param {string} body
|
|
36
|
-
* @returns {string | null}
|
|
37
|
-
*/
|
|
38
|
-
export function extractPolicyCapsuleSpan(body) {
|
|
39
|
-
const lines = splitLines(body);
|
|
40
|
-
let start = -1;
|
|
41
|
-
for (let i = 0; i < lines.length; i += 1) {
|
|
42
|
-
if (POLICY_HEADING_RE.test(lines[i])) {
|
|
43
|
-
start = i;
|
|
44
|
-
break;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
if (start === -1) return null;
|
|
48
|
-
|
|
49
|
-
let end = lines.length;
|
|
50
|
-
for (let i = start + 1; i < lines.length; i += 1) {
|
|
51
|
-
if (ANY_H2_RE.test(lines[i])) {
|
|
52
|
-
end = i;
|
|
53
|
-
break;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
return lines.slice(start, end).join('\n');
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* @param {object} skillsIndex
|
|
61
|
-
* @param {string} skillName
|
|
62
|
-
* @returns {object | null}
|
|
63
|
-
*/
|
|
64
|
-
function findSkillEntry(skillsIndex, skillName) {
|
|
65
|
-
const skills = skillsIndex?.skills;
|
|
66
|
-
if (!Array.isArray(skills)) return null;
|
|
67
|
-
return skills.find((entry) => entry.name === skillName) ?? null;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Load a skill's Policy Capsule. Returns the capsule span when the marker
|
|
72
|
-
* is present; falls back to the full SKILL.md body (and warns) only when the
|
|
73
|
-
* marker is missing, which signals a malformed manifest rather than an
|
|
74
|
-
* operator opt-in.
|
|
75
|
-
*
|
|
76
|
-
* @param {string} skillName
|
|
77
|
-
* @param {{ skills: Array<{ name: string, path: string }> }} skillsIndex
|
|
78
|
-
* @param {{
|
|
79
|
-
* repoRoot?: string,
|
|
80
|
-
* readFile?: (absPath: string) => string,
|
|
81
|
-
* warn?: (message: string) => void,
|
|
82
|
-
* }} [options]
|
|
83
|
-
* @returns {{ capsule: string, source: 'capsule' | 'full-body-fallback', path: string }}
|
|
84
|
-
*/
|
|
85
|
-
export function loadSkillCapsule(skillName, skillsIndex, options = {}) {
|
|
86
|
-
const {
|
|
87
|
-
repoRoot = PROJECT_ROOT,
|
|
88
|
-
readFile = (absPath) => fs.readFileSync(absPath, 'utf8'),
|
|
89
|
-
warn = (message) => Logger.warn(message),
|
|
90
|
-
} = options;
|
|
91
|
-
|
|
92
|
-
const entry = findSkillEntry(skillsIndex, skillName);
|
|
93
|
-
if (!entry?.path) {
|
|
94
|
-
throw new Error(
|
|
95
|
-
`loadSkillCapsule: skill "${skillName}" not found in skills index`,
|
|
96
|
-
);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const absPath = path.join(repoRoot, entry.path);
|
|
100
|
-
const body = readFile(absPath);
|
|
101
|
-
|
|
102
|
-
const span = extractPolicyCapsuleSpan(body);
|
|
103
|
-
if (span) {
|
|
104
|
-
return { capsule: span, source: 'capsule', path: entry.path };
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
warn(`capsule marker missing: ${skillName}`);
|
|
108
|
-
return { capsule: body, source: 'full-body-fallback', path: entry.path };
|
|
109
|
-
}
|
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* plan-critics.js — deterministic dispatch gate for the conditional
|
|
5
|
-
* author-step critics of the collapsed /plan flow (Epic #4474 PR6,
|
|
6
|
-
* design §4).
|
|
7
|
-
*
|
|
8
|
-
* **Thin shim (#4496 fix 6).** The evaluation itself now lives in
|
|
9
|
-
* `lib/orchestration/plan-critics-evaluate.js` and is folded into
|
|
10
|
-
* `plan-persist.js` as a pre-write phase, so the headless (`--yes`) path
|
|
11
|
-
* never pays a standalone CLI turn for the dispatch decision. This CLI
|
|
12
|
-
* survives one release for the attended pre-gate evaluation (the verdict
|
|
13
|
-
* folds into gate #2's view before the persist runs) and for any external
|
|
14
|
-
* scripting; it delegates to the shared module and keeps its exact output
|
|
15
|
-
* contract.
|
|
16
|
-
*
|
|
17
|
-
* Runs between authoring and gate #2, entirely git-local (zero GitHub
|
|
18
|
-
* calls): reads the authored artifacts, evaluates the risk/size dispatch
|
|
19
|
-
* conditions for the consolidation (8.3) and pre-mortem (8.5) critics via
|
|
20
|
-
* the shared evaluator, and emits one JSON verdict on stdout. The workflow
|
|
21
|
-
* dispatches a fresh-context sub-agent ONLY for critics with
|
|
22
|
-
* `dispatch: true`; every skip decision is appended to the plan-metrics
|
|
23
|
-
* ledger (`kind: "critic-skip"`, with reasons) so under-firing is
|
|
24
|
-
* auditable — the persist validators remain unchanged hard gates
|
|
25
|
-
* regardless of what this gate decides.
|
|
26
|
-
*
|
|
27
|
-
* Conditions (design §4 / §6 PR6):
|
|
28
|
-
* - Consolidation: the existing deterministic precondition
|
|
29
|
-
* (`evaluateConsolidationPrecondition`) says dispatch AND (the draft
|
|
30
|
-
* has > 5 stories OR a confirmed divergence from the Tech Spec's
|
|
31
|
-
* Delivery Slicing table). Skipped outright in the single-delivery
|
|
32
|
-
* shape (no tickets exist to consolidate).
|
|
33
|
-
* - Pre-mortem: risk verdict overall level is high, OR ticket count is
|
|
34
|
-
* at least half `maxTickets`, OR any `planning.riskHeuristics` phrase
|
|
35
|
-
* matches the plan text (case-insensitive substring over tech spec +
|
|
36
|
-
* tickets + risk summary).
|
|
37
|
-
*
|
|
38
|
-
* Modes:
|
|
39
|
-
* --epic <id> Artifact paths default to the per-Epic temp tree
|
|
40
|
-
* (`temp/epic-<id>/techspec.md`, `risk-verdict.json`,
|
|
41
|
-
* `tickets.json`); skip records land on the
|
|
42
|
-
* per-Epic plan-metrics ledger.
|
|
43
|
-
* explicit paths Ideation mode: pass --tech-spec/--risk-verdict
|
|
44
|
-
* (and --tickets when fan-out) explicitly; skip
|
|
45
|
-
* records land on the standalone ledger stream.
|
|
46
|
-
*
|
|
47
|
-
* Output (stdout-pure JSON):
|
|
48
|
-
* { epicId, consolidation: { critic, dispatch, reasons },
|
|
49
|
-
* premortem: { critic, dispatch, reasons } }
|
|
50
|
-
*
|
|
51
|
-
* Exit codes: 0 — verdict emitted (dispatch decisions are data, not
|
|
52
|
-
* failures); 1 — fatal error (unreadable/invalid artifacts, bad args).
|
|
53
|
-
*/
|
|
54
|
-
|
|
55
|
-
// Fail-fast if the framework's runtime deps are not installed — must be the
|
|
56
|
-
// first import so the check runs before any third-party-importing sibling
|
|
57
|
-
// module is evaluated (Story #3432).
|
|
58
|
-
import './lib/runtime-deps/ensure-installed.js';
|
|
59
|
-
import { readFile } from 'node:fs/promises';
|
|
60
|
-
import { parseArgs } from 'node:util';
|
|
61
|
-
|
|
62
|
-
import { runAsCli } from './lib/cli-utils.js';
|
|
63
|
-
import { epicArtifactPath } from './lib/config/temp-paths.js';
|
|
64
|
-
import {
|
|
65
|
-
resolveConfig,
|
|
66
|
-
validateOrchestrationConfig,
|
|
67
|
-
} from './lib/config-resolver.js';
|
|
68
|
-
import { routeAllOutputToStderr } from './lib/Logger.js';
|
|
69
|
-
import { loadRiskVerdict } from './lib/orchestration/epic-plan-spec/phases/risk-verdict.js';
|
|
70
|
-
import { evaluatePlanCritics } from './lib/orchestration/plan-critics-evaluate.js';
|
|
71
|
-
import {
|
|
72
|
-
appendCriticSkip,
|
|
73
|
-
recordPlanInvocation,
|
|
74
|
-
} from './lib/orchestration/plan-metrics.js';
|
|
75
|
-
|
|
76
|
-
const USAGE =
|
|
77
|
-
'Usage: plan-critics.js (--epic <EpicId> | --tech-spec <file> ' +
|
|
78
|
-
'--risk-verdict <file> [--tickets <file>]) [--pretty]';
|
|
79
|
-
|
|
80
|
-
async function readOptional(filePath, { required }) {
|
|
81
|
-
try {
|
|
82
|
-
return await readFile(filePath, 'utf8');
|
|
83
|
-
} catch (err) {
|
|
84
|
-
if (!required && err?.code === 'ENOENT') return null;
|
|
85
|
-
throw new Error(`Cannot read ${filePath}: ${err.message}`);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
async function main() {
|
|
90
|
-
const { values } = parseArgs({
|
|
91
|
-
options: {
|
|
92
|
-
epic: { type: 'string' },
|
|
93
|
-
'tech-spec': { type: 'string' },
|
|
94
|
-
'risk-verdict': { type: 'string' },
|
|
95
|
-
tickets: { type: 'string' },
|
|
96
|
-
pretty: { type: 'boolean', default: false },
|
|
97
|
-
},
|
|
98
|
-
strict: true,
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
let epicId = null;
|
|
102
|
-
if (values.epic !== undefined) {
|
|
103
|
-
epicId = Number.parseInt(values.epic, 10);
|
|
104
|
-
if (!Number.isInteger(epicId)) {
|
|
105
|
-
throw new Error(
|
|
106
|
-
`--epic must be a numeric issue id (got "${values.epic}").\n${USAGE}`,
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// stdout is reserved for the JSON verdict — flip every Logger sink to
|
|
112
|
-
// stderr before any pipeline code runs (same guarantee plan-context.js
|
|
113
|
-
// gives its envelope).
|
|
114
|
-
routeAllOutputToStderr();
|
|
115
|
-
|
|
116
|
-
let config;
|
|
117
|
-
try {
|
|
118
|
-
config = resolveConfig();
|
|
119
|
-
validateOrchestrationConfig(config);
|
|
120
|
-
} catch (err) {
|
|
121
|
-
throw new Error(`Config schema validation failed:\n${err.message}`);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const fallback = (basename) =>
|
|
125
|
-
epicId === null ? undefined : epicArtifactPath(epicId, basename, config);
|
|
126
|
-
const techSpecPath = values['tech-spec'] ?? fallback('techspec.md');
|
|
127
|
-
const riskVerdictPath =
|
|
128
|
-
values['risk-verdict'] ?? fallback('risk-verdict.json');
|
|
129
|
-
const ticketsPath = values.tickets ?? fallback('tickets.json');
|
|
130
|
-
if (!techSpecPath || !riskVerdictPath) {
|
|
131
|
-
throw new Error(
|
|
132
|
-
`Missing artifact path(s): without --epic, explicit --tech-spec and --risk-verdict are required.\n${USAGE}`,
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const verdict = await recordPlanInvocation(
|
|
137
|
-
{ cli: 'plan-critics', mode: 'evaluate', epicId, config },
|
|
138
|
-
async () => {
|
|
139
|
-
const techSpecContent = await readOptional(techSpecPath, {
|
|
140
|
-
required: true,
|
|
141
|
-
});
|
|
142
|
-
const riskVerdict = loadRiskVerdict(riskVerdictPath);
|
|
143
|
-
// Tickets are shape-dependent: a single-delivery plan authors none.
|
|
144
|
-
// Required only when passed explicitly.
|
|
145
|
-
const ticketsRaw = ticketsPath
|
|
146
|
-
? await readOptional(ticketsPath, {
|
|
147
|
-
required: values.tickets !== undefined,
|
|
148
|
-
})
|
|
149
|
-
: null;
|
|
150
|
-
let tickets = null;
|
|
151
|
-
if (ticketsRaw !== null) {
|
|
152
|
-
try {
|
|
153
|
-
tickets = JSON.parse(ticketsRaw);
|
|
154
|
-
} catch (err) {
|
|
155
|
-
throw new Error(
|
|
156
|
-
`Failed to parse tickets file "${ticketsPath}" as JSON: ${err.message}`,
|
|
157
|
-
);
|
|
158
|
-
}
|
|
159
|
-
if (!Array.isArray(tickets)) {
|
|
160
|
-
throw new Error(
|
|
161
|
-
`Tickets file "${ticketsPath}" must contain a JSON array.`,
|
|
162
|
-
);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const { consolidation, premortem } = evaluatePlanCritics({
|
|
167
|
-
techSpecContent,
|
|
168
|
-
riskVerdict,
|
|
169
|
-
tickets,
|
|
170
|
-
config,
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
// Skip-audit trail (#4474 PR6): every non-dispatch is a ledger
|
|
174
|
-
// record. Best-effort — a failed append never fails the gate.
|
|
175
|
-
for (const decision of [consolidation, premortem]) {
|
|
176
|
-
if (!decision.dispatch) {
|
|
177
|
-
await appendCriticSkip(
|
|
178
|
-
{
|
|
179
|
-
critic: decision.critic,
|
|
180
|
-
reasons: decision.reasons,
|
|
181
|
-
cli: 'plan-critics',
|
|
182
|
-
epicId,
|
|
183
|
-
},
|
|
184
|
-
config,
|
|
185
|
-
);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
return { epicId, consolidation, premortem };
|
|
190
|
-
},
|
|
191
|
-
);
|
|
192
|
-
|
|
193
|
-
const json = values.pretty
|
|
194
|
-
? JSON.stringify(verdict, null, 2)
|
|
195
|
-
: JSON.stringify(verdict);
|
|
196
|
-
process.stdout.write(`${json}\n`);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
runAsCli(import.meta.url, main, { source: 'plan-critics' });
|