@emiliosp/pi-maestro 0.11.0 → 0.13.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/ADR.md +82 -23
- package/CHANGELOG.md +48 -28
- package/MISSION.md +2 -4
- package/README.md +55 -25
- package/TECH_STACK.md +3 -3
- package/agents/builder.md +53 -57
- package/agents/simplifier.md +79 -61
- package/agents/verifier.md +52 -64
- package/docs/configuration.md +1 -1
- package/docs/glossary.md +43 -40
- package/docs/subagent-integration.md +7 -8
- package/docs/workflow.md +137 -63
- package/extensions/maestro-subagent.ts +1 -0
- package/extensions/maestro.ts +60 -30
- package/package.json +38 -40
- package/src/MaestroPaths.ts +22 -35
- package/src/artifacts/builder-handoff/assertBuilderHandoff.ts +6 -15
- package/src/artifacts/builder-handoff/schema.ts +3 -6
- package/src/artifacts/simplifier-handoff/assertSimplifierHandoff.ts +3 -3
- package/src/artifacts/simplifier-handoff/schema.ts +1 -0
- package/src/artifacts/verifier-handoff/assertVerifierHandoff.ts +4 -10
- package/src/artifacts/verifier-handoff/schema.ts +3 -6
- package/src/artifacts/verifier-handoff/writeVerifierHandoff.ts +1 -3
- package/src/config/assertConfiguration.ts +3 -6
- package/src/config/defaults.ts +1 -5
- package/src/config/loadConfiguration.ts +4 -11
- package/src/config/schema.ts +1 -2
- package/src/ids/createSpecId.ts +1 -2
- package/src/ids/isValidSpecId.ts +1 -2
- package/src/maestro/checks/assertEnvironment.ts +19 -50
- package/src/maestro/instructions/getMaestroInstructions.ts +47 -26
- package/src/maestro/session/MaestroSessionState.ts +15 -0
- package/src/maestro/status/formatMaestroStatus.ts +4 -13
- package/src/maestro/status/refreshMaestroStatus.ts +3 -7
- package/src/specs/create.ts +14 -4
- package/src/specs/template.ts +10 -14
- package/src/tools/child/record-builder-handoff.ts +1 -0
- package/src/tools/child/record-simplifier-handoff.ts +4 -4
- package/src/tools/child/record-verifier-handoff.ts +2 -3
- package/src/tools/child/utils/resolveWorkflowContext.ts +1 -4
- package/src/tools/main/create-spec.ts +3 -2
- package/src/tools/main/mark-spec-ready.ts +2 -1
- package/src/tools/main/resolve-escalations.ts +1 -0
- package/src/tools/main/resolve-findings.ts +6 -20
- package/src/tools/main/run-builder.ts +37 -57
- package/src/tools/main/run-simplifier.ts +10 -20
- package/src/tools/main/run-verifier.ts +31 -55
- package/src/tools/utils/pi-subagent-delegation.ts +13 -22
- package/src/tools/utils/resolveToolRunContext.ts +2 -3
- package/src/utils/is-errno-exception.ts +1 -3
- package/src/utils/path-exists.ts +1 -0
- package/src/utils/path-strictly-within.ts +3 -6
- package/src/utils/path-within-or-equal.ts +1 -4
- package/src/utils/read-json.ts +1 -4
- package/src/utils/write-json.ts +1 -4
- package/src/workflow/builder/completeBuilderPass.ts +3 -2
- package/src/workflow/builder/prepareBuilderRun.ts +3 -8
- package/src/workflow/builder/resolveEscalations.ts +2 -6
- package/src/workflow/builder/utils/getWorkflowEventByBuilderHandoff.ts +1 -3
- package/src/workflow/simplifier/completeSimplifierPass.ts +2 -3
- package/src/workflow/spec/markSpecReady.ts +10 -4
- package/src/workflow/state/readWorkflowState.ts +2 -7
- package/src/workflow/state/schema.ts +5 -10
- package/src/workflow/state/writeWorkflowState.ts +1 -4
- package/src/workflow/transitions.ts +3 -11
- package/src/workflow/utils/assertSpecAcceptanceCriteria.ts +2 -1
- package/src/workflow/utils/getAgentRole.ts +1 -3
- package/src/workflow/verifier/completeVerifierPass.ts +1 -0
- package/src/workflow/verifier/resolveFindings.ts +9 -29
- package/src/workflow/verifier/utils/getWorkflowEventByVerifierHandoff.ts +5 -20
- package/templates/design.md +20 -0
- package/templates/proposal.md +23 -0
- package/templates/spec.md +21 -50
- package/ROADMAP.md +0 -319
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { readFile } from 'node:fs/promises';
|
|
7
|
+
|
|
7
8
|
import type { MaestroPaths } from '#MaestroPaths.ts';
|
|
8
9
|
import { pathExists } from '#utils/path-exists.ts';
|
|
9
10
|
import { readWorkflowState } from '#workflow/state/readWorkflowState.ts';
|
|
@@ -12,7 +13,8 @@ import { writeWorkflowState } from '#workflow/state/writeWorkflowState.ts';
|
|
|
12
13
|
import { transitionWorkflow } from '#workflow/transitions.ts';
|
|
13
14
|
|
|
14
15
|
function assertAcceptanceCriterionOrdering(markdown: string): void {
|
|
15
|
-
|
|
16
|
+
// Match standalone ACs (###), including regression checks, and ACs nested under requirements (####).
|
|
17
|
+
const criteria = [...markdown.matchAll(/^#{3,4} AC(\d+):/gm)];
|
|
16
18
|
|
|
17
19
|
if (criteria.some((match, index) => Number(match[1]) !== index + 1)) {
|
|
18
20
|
throw new Error('Fix acceptance criterion ordering in spec.md.');
|
|
@@ -46,13 +48,17 @@ export const markSpecReady = async ({
|
|
|
46
48
|
throw new Error(`Spec file is missing: ${specFilePath}.`);
|
|
47
49
|
}
|
|
48
50
|
|
|
51
|
+
const proposalFilePath = paths.getProposalFilePath(specId);
|
|
52
|
+
|
|
53
|
+
if (!(await pathExists(proposalFilePath))) {
|
|
54
|
+
throw new Error(`Proposal file is missing: ${proposalFilePath}.`);
|
|
55
|
+
}
|
|
56
|
+
|
|
49
57
|
const workflowPath = paths.getWorkflowPath(specId);
|
|
50
58
|
const current = await readWorkflowState(workflowPath);
|
|
51
59
|
|
|
52
60
|
if (current.specId !== specId) {
|
|
53
|
-
throw new Error(
|
|
54
|
-
`Workflow spec ID mismatch: expected "${specId}", found "${current.specId}".`,
|
|
55
|
-
);
|
|
61
|
+
throw new Error(`Workflow spec ID mismatch: expected "${specId}", found "${current.specId}".`);
|
|
56
62
|
}
|
|
57
63
|
|
|
58
64
|
const state = transitionWorkflow({
|
|
@@ -4,14 +4,9 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { readJsonFile } from '#utils/read-json.ts';
|
|
7
|
-
import {
|
|
8
|
-
assertWorkflowState,
|
|
9
|
-
type WorkflowState,
|
|
10
|
-
} from '#workflow/state/schema.ts';
|
|
7
|
+
import { assertWorkflowState, type WorkflowState } from '#workflow/state/schema.ts';
|
|
11
8
|
|
|
12
|
-
export const readWorkflowState = async (
|
|
13
|
-
path: string,
|
|
14
|
-
): Promise<WorkflowState> => {
|
|
9
|
+
export const readWorkflowState = async (path: string): Promise<WorkflowState> => {
|
|
15
10
|
const state = await readJsonFile({ path, description: 'Workflow state' });
|
|
16
11
|
assertWorkflowState(state);
|
|
17
12
|
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { type Static, Type } from 'typebox';
|
|
7
7
|
import { Value } from 'typebox/value';
|
|
8
|
+
|
|
8
9
|
import { isValidSpecId, SPEC_ID_PATTERN } from '#ids/isValidSpecId.ts';
|
|
9
10
|
|
|
10
11
|
export const WORKFLOW_STATE_VERSION = '1.0.0';
|
|
@@ -58,17 +59,13 @@ export const WorkflowStateSchema = Type.Object(
|
|
|
58
59
|
{ additionalProperties: false },
|
|
59
60
|
);
|
|
60
61
|
|
|
61
|
-
export type WorkflowPhase =
|
|
62
|
-
(typeof WORKFLOW_PHASES)[keyof typeof WORKFLOW_PHASES];
|
|
62
|
+
export type WorkflowPhase = (typeof WORKFLOW_PHASES)[keyof typeof WORKFLOW_PHASES];
|
|
63
63
|
|
|
64
|
-
export type WorkflowEvent =
|
|
65
|
-
(typeof WORKFLOW_EVENTS)[keyof typeof WORKFLOW_EVENTS];
|
|
64
|
+
export type WorkflowEvent = (typeof WORKFLOW_EVENTS)[keyof typeof WORKFLOW_EVENTS];
|
|
66
65
|
|
|
67
66
|
export type WorkflowState = Static<typeof WorkflowStateSchema>;
|
|
68
67
|
|
|
69
|
-
function assertWorkflowStateSchema(
|
|
70
|
-
input: unknown,
|
|
71
|
-
): asserts input is WorkflowState {
|
|
68
|
+
function assertWorkflowStateSchema(input: unknown): asserts input is WorkflowState {
|
|
72
69
|
if (typeof input !== 'object' || input === null || Array.isArray(input)) {
|
|
73
70
|
throw new Error('Workflow state must be a JSON object.');
|
|
74
71
|
}
|
|
@@ -80,9 +77,7 @@ function assertWorkflowStateSchema(
|
|
|
80
77
|
}
|
|
81
78
|
}
|
|
82
79
|
|
|
83
|
-
export function assertWorkflowState(
|
|
84
|
-
input: unknown,
|
|
85
|
-
): asserts input is WorkflowState {
|
|
80
|
+
export function assertWorkflowState(input: unknown): asserts input is WorkflowState {
|
|
86
81
|
assertWorkflowStateSchema(input);
|
|
87
82
|
|
|
88
83
|
if (!isValidSpecId(input.specId)) {
|
|
@@ -4,10 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { writeJson } from '#utils/write-json.ts';
|
|
7
|
-
import {
|
|
8
|
-
assertWorkflowState,
|
|
9
|
-
type WorkflowState,
|
|
10
|
-
} from '#workflow/state/schema.ts';
|
|
7
|
+
import { assertWorkflowState, type WorkflowState } from '#workflow/state/schema.ts';
|
|
11
8
|
|
|
12
9
|
type WriteWorkflowStateInput = {
|
|
13
10
|
path: string;
|
|
@@ -35,10 +35,7 @@ export const WORKFLOW_TRANSITIONS: Record<WorkflowEvent, WorkflowTransition> = {
|
|
|
35
35
|
to: WORKFLOW_PHASES.READY_FOR_BUILDER,
|
|
36
36
|
},
|
|
37
37
|
[WORKFLOW_EVENTS.OWNER_REQUEST_SPEC_REVISION]: {
|
|
38
|
-
from: [
|
|
39
|
-
WORKFLOW_PHASES.ESCALATION_DECISIONS,
|
|
40
|
-
WORKFLOW_PHASES.FINDING_DECISIONS,
|
|
41
|
-
],
|
|
38
|
+
from: [WORKFLOW_PHASES.ESCALATION_DECISIONS, WORKFLOW_PHASES.FINDING_DECISIONS],
|
|
42
39
|
to: WORKFLOW_PHASES.DRAFTING_SPEC,
|
|
43
40
|
},
|
|
44
41
|
[WORKFLOW_EVENTS.BUILDER_DONE]: {
|
|
@@ -80,10 +77,7 @@ type TransitionWorkflowInput = {
|
|
|
80
77
|
event: WorkflowEvent;
|
|
81
78
|
};
|
|
82
79
|
|
|
83
|
-
export const transitionWorkflow = ({
|
|
84
|
-
state,
|
|
85
|
-
event,
|
|
86
|
-
}: TransitionWorkflowInput): WorkflowState => {
|
|
80
|
+
export const transitionWorkflow = ({ state, event }: TransitionWorkflowInput): WorkflowState => {
|
|
87
81
|
assertWorkflowState(state);
|
|
88
82
|
const transition = WORKFLOW_TRANSITIONS[event];
|
|
89
83
|
|
|
@@ -92,9 +86,7 @@ export const transitionWorkflow = ({
|
|
|
92
86
|
}
|
|
93
87
|
|
|
94
88
|
if (!transition.from.includes(state.phase)) {
|
|
95
|
-
throw new Error(
|
|
96
|
-
`Workflow event "${event}" is not allowed from phase "${state.phase}".`,
|
|
97
|
-
);
|
|
89
|
+
throw new Error(`Workflow event "${event}" is not allowed from phase "${state.phase}".`);
|
|
98
90
|
}
|
|
99
91
|
|
|
100
92
|
return {
|
|
@@ -12,7 +12,8 @@ export function assertSpecAcceptanceCriteria({
|
|
|
12
12
|
markdown,
|
|
13
13
|
criterionIds,
|
|
14
14
|
}: AssertSpecAcceptanceCriteriaInput): void {
|
|
15
|
-
|
|
15
|
+
// Match standalone ACs (###), including regression checks, and ACs nested under requirements (####).
|
|
16
|
+
const missingIds = [...markdown.matchAll(/^#{3,4} AC(\d+):/gm)]
|
|
16
17
|
.map((match) => `AC${match[1]}`)
|
|
17
18
|
.filter((id) => !criterionIds.has(id));
|
|
18
19
|
|
|
@@ -18,9 +18,7 @@ const VERIFIER_PHASES: readonly WorkflowState['phase'][] = [
|
|
|
18
18
|
WORKFLOW_PHASES.FINDING_DECISIONS,
|
|
19
19
|
];
|
|
20
20
|
|
|
21
|
-
export const getAgentRole = (
|
|
22
|
-
phase: WorkflowState['phase'],
|
|
23
|
-
): WorkflowRole | null => {
|
|
21
|
+
export const getAgentRole = (phase: WorkflowState['phase']): WorkflowRole | null => {
|
|
24
22
|
if (BUILDER_PHASES.includes(phase)) {
|
|
25
23
|
return WORKFLOW_ROLES.BUILDER;
|
|
26
24
|
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { mkdir, readFile } from 'node:fs/promises';
|
|
7
|
+
|
|
7
8
|
import { assertVerifierHandoff } from '#artifacts/verifier-handoff/assertVerifierHandoff.ts';
|
|
8
9
|
import type { VerifierHandoff } from '#artifacts/verifier-handoff/schema.ts';
|
|
9
10
|
import { writeVerifierHandoff } from '#artifacts/verifier-handoff/writeVerifierHandoff.ts';
|
|
@@ -5,10 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { assertVerifierHandoff } from '#artifacts/verifier-handoff/assertVerifierHandoff.ts';
|
|
7
7
|
import { readVerifierHandoff } from '#artifacts/verifier-handoff/readVerifierHandoff.ts';
|
|
8
|
-
import type {
|
|
9
|
-
VerifierFinding,
|
|
10
|
-
VerifierHandoff,
|
|
11
|
-
} from '#artifacts/verifier-handoff/schema.ts';
|
|
8
|
+
import type { VerifierFinding, VerifierHandoff } from '#artifacts/verifier-handoff/schema.ts';
|
|
12
9
|
import { FINDING_DECISIONS } from '#artifacts/verifier-handoff/schema.ts';
|
|
13
10
|
import type { MaestroPaths } from '#MaestroPaths.ts';
|
|
14
11
|
import { writeJson } from '#utils/write-json.ts';
|
|
@@ -48,20 +45,13 @@ const applyFindingDecisions = ({
|
|
|
48
45
|
}
|
|
49
46
|
|
|
50
47
|
if (decisionsById.has(decision.findingId)) {
|
|
51
|
-
throw new Error(
|
|
52
|
-
`Duplicate decision for finding "${decision.findingId}".`,
|
|
53
|
-
);
|
|
48
|
+
throw new Error(`Duplicate decision for finding "${decision.findingId}".`);
|
|
54
49
|
}
|
|
55
50
|
|
|
56
51
|
decisionsById.set(decision.findingId, decision);
|
|
57
52
|
|
|
58
|
-
if (
|
|
59
|
-
decision.
|
|
60
|
-
decision.reason.trim().length === 0
|
|
61
|
-
) {
|
|
62
|
-
throw new Error(
|
|
63
|
-
`Rejection reason for "${decision.findingId}" must not be empty.`,
|
|
64
|
-
);
|
|
53
|
+
if (decision.decision === FINDING_DECISIONS.REJECT && decision.reason.trim().length === 0) {
|
|
54
|
+
throw new Error(`Rejection reason for "${decision.findingId}" must not be empty.`);
|
|
65
55
|
}
|
|
66
56
|
}
|
|
67
57
|
|
|
@@ -94,15 +84,11 @@ const readFindingWorkflow = async ({
|
|
|
94
84
|
const state = await readWorkflowState(paths.getWorkflowPath(specId));
|
|
95
85
|
|
|
96
86
|
if (state.specId !== specId) {
|
|
97
|
-
throw new Error(
|
|
98
|
-
`Workflow spec ID mismatch: expected "${specId}", found "${state.specId}".`,
|
|
99
|
-
);
|
|
87
|
+
throw new Error(`Workflow spec ID mismatch: expected "${specId}", found "${state.specId}".`);
|
|
100
88
|
}
|
|
101
89
|
|
|
102
90
|
if (state.phase !== WORKFLOW_PHASES.FINDING_DECISIONS) {
|
|
103
|
-
throw new Error(
|
|
104
|
-
`Finding resolution requires finding-decisions state, found "${state.phase}".`,
|
|
105
|
-
);
|
|
91
|
+
throw new Error(`Finding resolution requires finding-decisions state, found "${state.phase}".`);
|
|
106
92
|
}
|
|
107
93
|
|
|
108
94
|
const handoff = await readVerifierHandoff({
|
|
@@ -111,23 +97,17 @@ const readFindingWorkflow = async ({
|
|
|
111
97
|
});
|
|
112
98
|
|
|
113
99
|
if (handoff.findings.length === 0) {
|
|
114
|
-
throw new Error(
|
|
115
|
-
'Finding resolution requires at least one current finding.',
|
|
116
|
-
);
|
|
100
|
+
throw new Error('Finding resolution requires at least one current finding.');
|
|
117
101
|
}
|
|
118
102
|
|
|
119
103
|
if (handoff.findings.some(({ decision }) => decision !== null)) {
|
|
120
|
-
throw new Error(
|
|
121
|
-
'Current verifier findings already contain an owner decision.',
|
|
122
|
-
);
|
|
104
|
+
throw new Error('Current verifier findings already contain an owner decision.');
|
|
123
105
|
}
|
|
124
106
|
|
|
125
107
|
return { state, handoff };
|
|
126
108
|
};
|
|
127
109
|
|
|
128
|
-
const getSavedFindingDecision = (
|
|
129
|
-
decision: FindingDecision | undefined,
|
|
130
|
-
): SavedFindingDecision => {
|
|
110
|
+
const getSavedFindingDecision = (decision: FindingDecision | undefined): SavedFindingDecision => {
|
|
131
111
|
if (decision?.decision === FINDING_DECISIONS.REVISE_SPEC) {
|
|
132
112
|
return {
|
|
133
113
|
decision: FINDING_DECISIONS.REVISE_SPEC,
|
|
@@ -3,15 +3,10 @@
|
|
|
3
3
|
* Used: When a verifier completes a pass or the owner resolves its findings.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
FINDING_DECISIONS,
|
|
8
|
-
type VerifierHandoff,
|
|
9
|
-
} from '#artifacts/verifier-handoff/schema.ts';
|
|
6
|
+
import { FINDING_DECISIONS, type VerifierHandoff } from '#artifacts/verifier-handoff/schema.ts';
|
|
10
7
|
import { WORKFLOW_EVENTS, type WorkflowEvent } from '#workflow/state/schema.ts';
|
|
11
8
|
|
|
12
|
-
export const getWorkflowEventByVerifierHandoff = (
|
|
13
|
-
handoff: VerifierHandoff,
|
|
14
|
-
): WorkflowEvent => {
|
|
9
|
+
export const getWorkflowEventByVerifierHandoff = (handoff: VerifierHandoff): WorkflowEvent => {
|
|
15
10
|
if (handoff.findings.length === 0) {
|
|
16
11
|
return WORKFLOW_EVENTS.VERIFIER_DONE;
|
|
17
12
|
}
|
|
@@ -21,26 +16,16 @@ export const getWorkflowEventByVerifierHandoff = (
|
|
|
21
16
|
}
|
|
22
17
|
|
|
23
18
|
if (
|
|
24
|
-
handoff.findings.some(
|
|
25
|
-
({ decision }) => decision?.decision === FINDING_DECISIONS.REVISE_SPEC,
|
|
26
|
-
)
|
|
19
|
+
handoff.findings.some(({ decision }) => decision?.decision === FINDING_DECISIONS.REVISE_SPEC)
|
|
27
20
|
) {
|
|
28
21
|
return WORKFLOW_EVENTS.OWNER_REQUEST_SPEC_REVISION;
|
|
29
22
|
}
|
|
30
23
|
|
|
31
|
-
if (
|
|
32
|
-
handoff.findings.some(
|
|
33
|
-
({ decision }) => decision?.decision === FINDING_DECISIONS.FIX_CODE,
|
|
34
|
-
)
|
|
35
|
-
) {
|
|
24
|
+
if (handoff.findings.some(({ decision }) => decision?.decision === FINDING_DECISIONS.FIX_CODE)) {
|
|
36
25
|
return WORKFLOW_EVENTS.OWNER_REQUEST_FIXES;
|
|
37
26
|
}
|
|
38
27
|
|
|
39
|
-
if (
|
|
40
|
-
handoff.findings.every(
|
|
41
|
-
({ decision }) => decision?.decision === FINDING_DECISIONS.REJECT,
|
|
42
|
-
)
|
|
43
|
-
) {
|
|
28
|
+
if (handoff.findings.every(({ decision }) => decision?.decision === FINDING_DECISIONS.REJECT)) {
|
|
44
29
|
return WORKFLOW_EVENTS.OWNER_REJECT_FINDINGS;
|
|
45
30
|
}
|
|
46
31
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# <id>: <short, outcome-oriented title>
|
|
2
|
+
|
|
3
|
+
<!--
|
|
4
|
+
Create this document only when significant technical decisions must be preserved.
|
|
5
|
+
Record approved architectural decisions, important technical constraints, or prototype choices, not routine implementation details.
|
|
6
|
+
Keep motivation and scope in proposal.md. Keep required behavior, edge cases, and acceptance criteria in spec.md.
|
|
7
|
+
Use only the sections that matter. Omit empty sections and these instructions.
|
|
8
|
+
-->
|
|
9
|
+
|
|
10
|
+
## Decisions
|
|
11
|
+
|
|
12
|
+
<Describe each approved technical decision and its brief reason. Include only alternatives actually considered and consequences that matter. Link related REQ IDs in spec.md instead of repeating requirements.>
|
|
13
|
+
|
|
14
|
+
## Contracts and prototypes
|
|
15
|
+
|
|
16
|
+
<Include interface or data contracts and links to prototypes only when needed to explain the approved design. Keep observable behavior in spec.md and distinguish illustrative prototype content from requirements.>
|
|
17
|
+
|
|
18
|
+
## Risks and limits
|
|
19
|
+
|
|
20
|
+
<Record relevant technical risks, migration or rollback constraints, and evidence supporting the decision. Do not add investigation logs. Resolve decisions affecting the contract with the owner before approval.>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# <id>: <short, outcome-oriented title>
|
|
2
|
+
|
|
3
|
+
<!--
|
|
4
|
+
Explain why this change exists and where its scope ends. Keep it brief and write for the owner.
|
|
5
|
+
Keep detailed behavior and acceptance criteria in spec.md, and approved technical decisions in design.md when needed.
|
|
6
|
+
Omit empty sections and these instructions. Do not duplicate requirements or investigation logs.
|
|
7
|
+
-->
|
|
8
|
+
|
|
9
|
+
## Problem
|
|
10
|
+
|
|
11
|
+
<Describe the current problem, who is affected, and why the change is needed.>
|
|
12
|
+
|
|
13
|
+
## Goal and scope
|
|
14
|
+
|
|
15
|
+
<State the intended outcome and the boundaries of this one reversible change.>
|
|
16
|
+
|
|
17
|
+
## Out of scope
|
|
18
|
+
|
|
19
|
+
<Name related changes that are explicitly excluded.>
|
|
20
|
+
|
|
21
|
+
## Required behavior
|
|
22
|
+
|
|
23
|
+
See [spec.md](spec.md).
|
package/templates/spec.md
CHANGED
|
@@ -1,69 +1,40 @@
|
|
|
1
1
|
# <id>: <short, outcome-oriented title>
|
|
2
2
|
|
|
3
|
-
> After owner approval, this specification is the contract for the builder and verifier.
|
|
4
|
-
|
|
5
3
|
<!--
|
|
6
|
-
|
|
7
|
-
State each requirement once
|
|
8
|
-
Use the topics below as guidance
|
|
9
|
-
Leave
|
|
4
|
+
Define required behavior, not motivation or implementation. See proposal.md for scope and design.md, when present, for approved technical decisions.
|
|
5
|
+
Write for the owner. State each requirement once and keep its acceptance criteria immediately below it.
|
|
6
|
+
Use the topics below as guidance. Omit empty subsections and these instructions.
|
|
7
|
+
Leave test code, fixtures, mocks, and commands to the builder. Keep execution evidence in handoffs.
|
|
10
8
|
-->
|
|
11
9
|
|
|
12
|
-
##
|
|
13
|
-
|
|
14
|
-
<Briefly describe the problem, who is affected, and the intended outcome.>
|
|
15
|
-
|
|
16
|
-
### Out of scope
|
|
17
|
-
|
|
18
|
-
<Name related changes that are explicitly excluded.>
|
|
19
|
-
|
|
20
|
-
## 2. Requirements and constraints
|
|
10
|
+
## Requirements
|
|
21
11
|
|
|
22
|
-
<
|
|
12
|
+
<Give each requirement its own ### REQ<number>: <title> heading and a unique, stable ID. Document only behavior or constraints this change adds, modifies, or removes, with relevant measurable limits, edge cases, and error handling. Existing behavior must continue working unless explicitly changed or removed; do not list unchanged behavior as requirements. Do not copy repository or agent instructions.>
|
|
23
13
|
|
|
24
|
-
<
|
|
14
|
+
<Give each requirement exactly one Delta value: added, modified, or removed. Keep requirements organized by topic, not by delta category. For modified or removed requirements, add a brief Before description only when useful. Verify the previous behavior from repository evidence; do not invent it or repeat the whole old requirement. State the complete required outcome, including the required absence and any agreed replacement or error behavior for removals. Keep the same pre-change baseline during revisions.>
|
|
25
15
|
|
|
26
|
-
|
|
16
|
+
<Each requirement must have Verified by with Markdown links to its acceptance criteria, using their actual heading anchors. When an existing test covers the exact starting conditions, action, and expected result, link its file relative to spec.md and give the exact scenario name instead of creating a duplicate criterion. Keep criteria for partial or uncertain coverage. A requirement can reference both criteria and tests; check their combined coverage.>
|
|
27
17
|
|
|
28
|
-
<
|
|
18
|
+
<Write each requirement-associated AC once, immediately below its primary requirement, with a #### AC<number>: <title> heading. Other requirements can link to it without duplication. A relevant regression AC without an associated changed requirement can instead use a ### AC<number>: <title> heading under an optional ## Regression checks section, with the same probe, expected result, and example fields. Do not invent a REQ or Delta field for it. Include only regressions relevant to this change, not an inventory of existing behavior, and omit the section when unnecessary.>
|
|
29
19
|
|
|
30
|
-
|
|
20
|
+
<Number ACs continuously from AC1 across the whole document, including nested and standalone regression criteria. During review, remove criteria already covered exactly by existing tests and update numbering and affected links. Keep required behavior documented when a requirement-associated criterion is omitted; omitting a standalone regression criterion does not require creating a requirement.>
|
|
31
21
|
|
|
32
|
-
|
|
22
|
+
### REQ1: <short required behavior>
|
|
33
23
|
|
|
34
|
-
<
|
|
24
|
+
Delta: <added | modified | removed>
|
|
35
25
|
|
|
36
|
-
|
|
37
|
-
2. API specification: changed operations, permissions, inputs, outputs, errors, side effects, and delivery guarantees.
|
|
38
|
-
3. Prototype and user interaction: link prototypes and identify approved states and interactions. Distinguish illustrative content from requirements.
|
|
39
|
-
4. External integrations: affected services, failure behavior, and retries.
|
|
40
|
-
5. Security and privacy: access rules, sensitive data, retention, and trust boundaries.
|
|
41
|
-
6. Compatibility and migration: compatibility limits, migration, rollout, and rollback requirements.
|
|
42
|
-
7. Monitoring and observability: required signals, alerts, and ownership.
|
|
26
|
+
<Describe the requirement once, including relevant boundary conditions and required failure or recovery behavior.>
|
|
43
27
|
|
|
44
|
-
|
|
28
|
+
Verified by: [AC1](#ac1-short-observable-claim)
|
|
45
29
|
|
|
46
|
-
|
|
30
|
+
#### AC1: <short observable claim>
|
|
47
31
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
### AC1: <short observable claim>
|
|
53
|
-
|
|
54
|
-
1. Coverage: <success | failure | unchanged>
|
|
55
|
-
2. Probe: <starting conditions and action or observation, without prescribing test implementation>
|
|
56
|
-
3. Expected result: <observable and measurable outcome>
|
|
57
|
-
4. Example: <specific starting conditions, concrete input or action, and exact expected result for this probe, not a restatement of the claim>
|
|
32
|
+
1. Probe: <starting conditions and action or observation, without prescribing test implementation>
|
|
33
|
+
2. Expected result: <observable and measurable outcome>
|
|
34
|
+
3. Example: <specific starting conditions, concrete input or action, and exact expected result, not a restatement of the claim>
|
|
58
35
|
|
|
59
36
|
<!--
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
Expected result: The saved title is still present.
|
|
64
|
-
Example: An item's title is "Draft". Change it to "Ready", save, and reopen the item. The title is "Ready".
|
|
65
|
-
|
|
66
|
-
The builder and verifier each run every probe.
|
|
67
|
-
The builder chooses executable checks. The verifier checks their coverage independently.
|
|
68
|
-
Keep execution evidence in handoffs, not in this spec.
|
|
37
|
+
Each AC has one observable claim, a probe, an expected result, and a concrete example.
|
|
38
|
+
Describe relevant valid actions, invalid actions, and regressions directly through those fields, without a separate classification label.
|
|
39
|
+
The builder and verifier each run every probe independently. The builder chooses executable checks; the verifier checks their coverage independently.
|
|
69
40
|
-->
|