@gordon.gan/specflow 1.8.0-beta → 1.8.2-beta
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/README.md +7 -5
- package/dist/cli/commands/document-run.d.ts +98 -0
- package/dist/cli/commands/document-run.js +1064 -0
- package/dist/cli/index.js +2 -2
- package/dist/core/artifact-language.js +11 -1
- package/dist/core/document/asset-paths.d.ts +15 -0
- package/dist/core/document/asset-paths.js +38 -0
- package/dist/core/document/chapters.d.ts +6 -0
- package/dist/core/document/chapters.js +38 -0
- package/dist/core/document/coverage.d.ts +25 -0
- package/dist/core/document/coverage.js +49 -0
- package/dist/core/document/digests.d.ts +40 -0
- package/dist/core/document/digests.js +144 -0
- package/dist/core/document/engine.d.ts +101 -0
- package/dist/core/document/engine.js +722 -0
- package/dist/core/document/extract.d.ts +61 -0
- package/dist/core/document/extract.js +437 -0
- package/dist/core/{approval → document}/forbidden-patterns.d.ts +4 -2
- package/dist/core/document/forbidden-patterns.js +36 -0
- package/dist/core/document/gates.d.ts +39 -0
- package/dist/core/document/gates.js +75 -0
- package/dist/core/document/input-digest.d.ts +63 -0
- package/dist/core/document/input-digest.js +200 -0
- package/dist/core/document/input-features.d.ts +43 -0
- package/dist/core/document/input-features.js +161 -0
- package/dist/core/document/lint.d.ts +39 -0
- package/dist/core/document/lint.js +147 -0
- package/dist/core/document/llm.d.ts +28 -0
- package/dist/core/document/llm.js +22 -0
- package/dist/core/document/map.d.ts +52 -0
- package/dist/core/document/map.js +296 -0
- package/dist/core/document/outline.d.ts +29 -0
- package/dist/core/document/outline.js +171 -0
- package/dist/core/document/paths.d.ts +23 -0
- package/dist/core/document/paths.js +36 -0
- package/dist/core/document/profile-validator.d.ts +23 -0
- package/dist/core/document/profile-validator.js +155 -0
- package/dist/core/document/profiles.d.ts +23 -0
- package/dist/core/document/profiles.js +76 -0
- package/dist/core/document/render.d.ts +61 -0
- package/dist/core/document/render.js +243 -0
- package/dist/core/document/review.d.ts +57 -0
- package/dist/core/document/review.js +202 -0
- package/dist/core/document/scene-detect.d.ts +32 -0
- package/dist/core/document/scene-detect.js +237 -0
- package/dist/core/document/schemas.d.ts +1111 -0
- package/dist/core/document/schemas.js +191 -0
- package/dist/integrations/shared/capability-evidence.js +4 -9
- package/dist/integrations/shared/command-catalog.js +2 -1
- package/dist/integrations/shared/parity-manifest.js +4 -9
- package/package.json +2 -1
- package/prompts/apply/phase-a-plan.md +1 -1
- package/prompts/document/map/acceptance.md +9 -0
- package/prompts/document/map/anti-ai.md +29 -0
- package/prompts/document/map/api-design.md +26 -0
- package/prompts/document/map/architecture.md +27 -0
- package/prompts/document/map/benchmark.md +26 -0
- package/prompts/document/map/closed-loop.md +9 -0
- package/prompts/document/map/compat-migration.md +31 -0
- package/prompts/document/map/component-design.md +30 -0
- package/prompts/document/map/config-runtime.md +9 -0
- package/prompts/document/map/core-flow.md +62 -0
- package/prompts/document/map/core-logic.md +9 -0
- package/prompts/document/map/data-model.md +18 -0
- package/prompts/document/map/deploy.md +26 -0
- package/prompts/document/map/fix.md +8 -0
- package/prompts/document/map/frontend-architecture.md +35 -0
- package/prompts/document/map/goal.md +8 -0
- package/prompts/document/map/impact.md +8 -0
- package/prompts/document/map/implementability.md +9 -0
- package/prompts/document/map/migration-guide.md +36 -0
- package/prompts/document/map/mvp-boundary.md +8 -0
- package/prompts/document/map/non-goals.md +8 -0
- package/prompts/document/map/ops.md +33 -0
- package/prompts/document/map/performance.md +32 -0
- package/prompts/document/map/poc-demo.md +25 -0
- package/prompts/document/map/regression.md +8 -0
- package/prompts/document/map/reproduce.md +8 -0
- package/prompts/document/map/requirement.md +8 -0
- package/prompts/document/map/research.md +25 -0
- package/prompts/document/map/root-cause.md +8 -0
- package/prompts/document/map/signoff.md +9 -0
- package/prompts/document/map/state-management.md +23 -0
- package/prompts/document/map/tech-selection.md +29 -0
- package/prompts/document/map/test-strategy.md +24 -0
- package/prompts/document/map/ui-design.md +21 -0
- package/prompts/document/outline/general.md +46 -0
- package/prompts/document/review/ai-review.md +57 -0
- package/prompts/document/shared/grounding.md +84 -0
- package/prompts/explore/explore-session.md +1 -1
- package/prompts/propose/design-draft.md +1 -1
- package/prompts/propose/proposal.md +1 -1
- package/prompts/propose/specs.md +1 -1
- package/prompts/propose/tasks-draft.md +1 -1
- package/prompts/refine/design-output.md +1 -1
- package/prompts/refine/update-artifacts.md +2 -0
- package/prompts/shared/artifact-language.md +13 -0
- package/skills/specflow-techdoc/SKILL.md +143 -0
- package/skills/specflow-techdoc-synth/SKILL.md +115 -0
- package/templates/document/chapters/acceptance.yaml +16 -0
- package/templates/document/chapters/api-design.yaml +29 -0
- package/templates/document/chapters/architecture.yaml +21 -0
- package/templates/document/chapters/benchmark.yaml +20 -0
- package/templates/document/chapters/closed-loop.yaml +22 -0
- package/templates/document/chapters/compat-migration.yaml +21 -0
- package/templates/document/chapters/component-design.yaml +22 -0
- package/templates/document/chapters/config-runtime.yaml +16 -0
- package/templates/document/chapters/core-flow.yaml +27 -0
- package/templates/document/chapters/core-logic.yaml +16 -0
- package/templates/document/chapters/data-model.yaml +25 -0
- package/templates/document/chapters/deploy.yaml +22 -0
- package/templates/document/chapters/fix.yaml +18 -0
- package/templates/document/chapters/frontend-architecture.yaml +22 -0
- package/templates/document/chapters/goal.yaml +17 -0
- package/templates/document/chapters/impact.yaml +16 -0
- package/templates/document/chapters/implementability.yaml +22 -0
- package/templates/document/chapters/migration-guide.yaml +21 -0
- package/templates/document/chapters/mvp-boundary.yaml +20 -0
- package/templates/document/chapters/non-goals.yaml +17 -0
- package/templates/document/chapters/ops.yaml +25 -0
- package/templates/document/chapters/performance.yaml +21 -0
- package/templates/document/chapters/poc-demo.yaml +22 -0
- package/templates/document/chapters/regression.yaml +16 -0
- package/templates/document/chapters/reproduce.yaml +16 -0
- package/templates/document/chapters/requirement.yaml +18 -0
- package/templates/document/chapters/research.yaml +22 -0
- package/templates/document/chapters/root-cause.yaml +18 -0
- package/templates/document/chapters/signoff.yaml +15 -0
- package/templates/document/chapters/state-management.yaml +22 -0
- package/templates/document/chapters/tech-selection.yaml +23 -0
- package/templates/document/chapters/test-strategy.yaml +23 -0
- package/templates/document/chapters/ui-design.yaml +28 -0
- package/templates/document/profiles/0to1.yaml +44 -0
- package/templates/document/profiles/approve.yaml +22 -0
- package/templates/document/profiles/bugfix.yaml +15 -0
- package/templates/document/profiles/feature.yaml +23 -0
- package/templates/document/profiles/frontend-0to1.yaml +47 -0
- package/templates/document/profiles/migration.yaml +42 -0
- package/templates/document/profiles/poc.yaml +46 -0
- package/dist/cli/commands/approval-assemble.d.ts +0 -64
- package/dist/cli/commands/approval-assemble.js +0 -408
- package/dist/core/approval/assemble.d.ts +0 -10
- package/dist/core/approval/assemble.js +0 -384
- package/dist/core/approval/bundle.d.ts +0 -9
- package/dist/core/approval/bundle.js +0 -172
- package/dist/core/approval/forbidden-patterns.js +0 -40
- package/dist/core/approval/index-schema.d.ts +0 -676
- package/dist/core/approval/index-schema.js +0 -193
- package/dist/core/approval/index.d.ts +0 -12
- package/dist/core/approval/index.js +0 -9
- package/dist/core/approval/lint.d.ts +0 -10
- package/dist/core/approval/lint.js +0 -302
- package/dist/core/approval/paths.d.ts +0 -13
- package/dist/core/approval/paths.js +0 -43
- package/dist/core/approval/pipeline.d.ts +0 -28
- package/dist/core/approval/pipeline.js +0 -146
- package/dist/core/approval/playbook-schema.d.ts +0 -182
- package/dist/core/approval/playbook-schema.js +0 -51
- package/dist/core/approval/render.d.ts +0 -20
- package/dist/core/approval/render.js +0 -210
- package/dist/core/approval/review-pack.d.ts +0 -26
- package/dist/core/approval/review-pack.js +0 -205
- package/dist/core/approval/types.d.ts +0 -223
- package/dist/core/approval/types.js +0 -1
- package/prompts/approval/acp-pipeline.md +0 -106
- package/prompts/approval/ai-review.md +0 -145
- package/prompts/approval/api-guidance.md +0 -179
- package/prompts/approval/database-guidance.md +0 -105
- package/prompts/approval/frontend-guidance.md +0 -249
- package/prompts/approval/generate.md +0 -1596
- package/prompts/approval/multi-repo-guidance.md +0 -263
- package/prompts/approval/multi-repo-spoke-subagent.md +0 -94
- package/prompts/approval/project-conventions-guidance.md +0 -171
- package/prompts/approval/runtime-guidance.md +0 -64
- package/prompts/approval/segmented-generation.md +0 -157
- package/skills/specflow-approval/SKILL.md +0 -778
- package/templates/approval-index.yaml +0 -93
- package/templates/approval-part.md +0 -15
- package/templates/approval-playbook-talos-scenario-job-compile.yaml +0 -29
- package/templates/approval-playbook.yaml +0 -28
- package/templates/approval.md +0 -261
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
const tableRefSchema = z.object({
|
|
3
|
-
id: z.string().min(1),
|
|
4
|
-
name: z.string().min(1),
|
|
5
|
-
action: z.string().min(1),
|
|
6
|
-
part: z.string().min(1),
|
|
7
|
-
repo: z.string().min(1).optional(),
|
|
8
|
-
});
|
|
9
|
-
const interfaceFrozenSchema = z.object({
|
|
10
|
-
rpc: z.string().min(1).optional(),
|
|
11
|
-
http_path: z.string().min(1).optional(),
|
|
12
|
-
status_enum: z.array(z.string().min(1)).optional(),
|
|
13
|
-
});
|
|
14
|
-
const interfaceRefSchema = z.object({
|
|
15
|
-
id: z.string().min(1),
|
|
16
|
-
short: z.string().min(1),
|
|
17
|
-
change: z.string().min(1),
|
|
18
|
-
part: z.string().min(1),
|
|
19
|
-
repo: z.string().min(1).optional(),
|
|
20
|
-
layers: z.array(z.string().min(1)).optional(),
|
|
21
|
-
pairs_with: z.string().min(1).optional(),
|
|
22
|
-
feature_slice: z.string().min(1).optional(),
|
|
23
|
-
frozen: interfaceFrozenSchema.optional(),
|
|
24
|
-
});
|
|
25
|
-
const pageRefSchema = z.object({
|
|
26
|
-
id: z.string().min(1),
|
|
27
|
-
route: z.string().min(1),
|
|
28
|
-
apis: z.array(z.string()).default([]),
|
|
29
|
-
part: z.string().min(1),
|
|
30
|
-
repo: z.string().min(1).optional(),
|
|
31
|
-
});
|
|
32
|
-
const capabilityRefSchema = z.object({
|
|
33
|
-
id: z.string().min(1),
|
|
34
|
-
part: z.string().min(1),
|
|
35
|
-
});
|
|
36
|
-
const outputRefSchema = z.object({
|
|
37
|
-
repo: z.string().min(1),
|
|
38
|
-
change: z.string().min(1),
|
|
39
|
-
path: z.string().min(1),
|
|
40
|
-
});
|
|
41
|
-
const multiRepoSchema = z
|
|
42
|
-
.object({
|
|
43
|
-
enabled: z.boolean(),
|
|
44
|
-
document_mode: z.enum(['unified', 'per_repo']).optional(),
|
|
45
|
-
primary_repo: z.string().min(1).optional(),
|
|
46
|
-
repos: z
|
|
47
|
-
.array(z.object({
|
|
48
|
-
id: z.string().min(1),
|
|
49
|
-
label: z.string().min(1),
|
|
50
|
-
role: z.enum(['platform', 'web', 'worker', 'hub', 'other']).optional(),
|
|
51
|
-
change: z.string().min(1),
|
|
52
|
-
root_hint: z.string().min(1).optional(),
|
|
53
|
-
}))
|
|
54
|
-
.min(1),
|
|
55
|
-
outputs: z
|
|
56
|
-
.object({
|
|
57
|
-
unified: outputRefSchema.optional(),
|
|
58
|
-
per_repo: z.array(outputRefSchema).optional(),
|
|
59
|
-
})
|
|
60
|
-
.optional(),
|
|
61
|
-
bundle: z
|
|
62
|
-
.object({
|
|
63
|
-
enabled: z.boolean().optional(),
|
|
64
|
-
playbook: z.string().min(1).optional(),
|
|
65
|
-
output: z.string().min(1).optional(),
|
|
66
|
-
readonly: z.boolean().optional(),
|
|
67
|
-
})
|
|
68
|
-
.optional(),
|
|
69
|
-
})
|
|
70
|
-
.superRefine((value, ctx) => {
|
|
71
|
-
if (!value.enabled) {
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
if (value.repos.length < 2) {
|
|
75
|
-
ctx.addIssue({
|
|
76
|
-
code: z.ZodIssueCode.custom,
|
|
77
|
-
message: 'multi_repo.repos must have at least 2 entries when enabled',
|
|
78
|
-
path: ['repos'],
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
if (value.document_mode === 'unified' && !value.primary_repo) {
|
|
82
|
-
ctx.addIssue({
|
|
83
|
-
code: z.ZodIssueCode.custom,
|
|
84
|
-
message: 'multi_repo.primary_repo is required when document_mode is unified',
|
|
85
|
-
path: ['primary_repo'],
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
if (value.primary_repo && !value.repos.some((r) => r.id === value.primary_repo)) {
|
|
89
|
-
ctx.addIssue({
|
|
90
|
-
code: z.ZodIssueCode.custom,
|
|
91
|
-
message: 'multi_repo.primary_repo must match a repos[].id',
|
|
92
|
-
path: ['primary_repo'],
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
if (value.bundle?.enabled === true && !value.primary_repo) {
|
|
96
|
-
ctx.addIssue({
|
|
97
|
-
code: z.ZodIssueCode.custom,
|
|
98
|
-
message: 'multi_repo.primary_repo is required when bundle.enabled is true',
|
|
99
|
-
path: ['primary_repo'],
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
});
|
|
103
|
-
export const approvalIndexSchema = z.object({
|
|
104
|
-
schema: z.literal('specflow.approval.index/v1'),
|
|
105
|
-
change: z.string().min(1),
|
|
106
|
-
generated_at: z.preprocess((val) => (val instanceof Date ? val.toISOString() : val), z.string().min(1)),
|
|
107
|
-
mode: z.enum(['segmented', 'monolithic']).default('segmented'),
|
|
108
|
-
ui_in_scope: z.boolean().default(false),
|
|
109
|
-
db_in_scope: z.boolean().default(false),
|
|
110
|
-
api_in_scope: z.boolean().default(false),
|
|
111
|
-
optional: z
|
|
112
|
-
.object({
|
|
113
|
-
s5: z.boolean().default(false),
|
|
114
|
-
s7: z.boolean().default(false),
|
|
115
|
-
s8: z.boolean().default(false),
|
|
116
|
-
})
|
|
117
|
-
.default({ s5: false, s7: false, s8: false }),
|
|
118
|
-
parts_order: z.array(z.string().min(1)).min(1),
|
|
119
|
-
design_points: z.array(z.string()).default([]),
|
|
120
|
-
decisions: z.array(z.string()).default([]),
|
|
121
|
-
tables: z.array(tableRefSchema).default([]),
|
|
122
|
-
interfaces: z.array(interfaceRefSchema).default([]),
|
|
123
|
-
pages: z.array(pageRefSchema).default([]),
|
|
124
|
-
capabilities: z.array(capabilityRefSchema).default([]),
|
|
125
|
-
batching: z
|
|
126
|
-
.object({
|
|
127
|
-
tables_per_call: z.number().int().positive().default(3),
|
|
128
|
-
interfaces_per_call: z.number().int().positive().default(3),
|
|
129
|
-
pages_per_call: z.number().int().positive().default(3),
|
|
130
|
-
capabilities_per_call: z.number().int().positive().default(1),
|
|
131
|
-
})
|
|
132
|
-
.default({
|
|
133
|
-
tables_per_call: 3,
|
|
134
|
-
interfaces_per_call: 3,
|
|
135
|
-
pages_per_call: 3,
|
|
136
|
-
capabilities_per_call: 1,
|
|
137
|
-
}),
|
|
138
|
-
conventions: z
|
|
139
|
-
.object({
|
|
140
|
-
architecture: z.array(z.string()).optional(),
|
|
141
|
-
database: z.array(z.string()).optional(),
|
|
142
|
-
api: z.array(z.string()).optional(),
|
|
143
|
-
frontend: z.array(z.string()).optional(),
|
|
144
|
-
})
|
|
145
|
-
.default({}),
|
|
146
|
-
meta: z
|
|
147
|
-
.object({
|
|
148
|
-
language: z.string().optional(),
|
|
149
|
-
tech_stack: z.string().optional(),
|
|
150
|
-
project_mode: z.string().optional(),
|
|
151
|
-
})
|
|
152
|
-
.optional(),
|
|
153
|
-
multi_repo: multiRepoSchema.optional(),
|
|
154
|
-
acp: z
|
|
155
|
-
.object({
|
|
156
|
-
pipeline: z.enum(['acp/v1', 'acp/v2']).optional(),
|
|
157
|
-
context_budget_tokens: z.number().int().positive().optional(),
|
|
158
|
-
output_budget_tokens: z.number().int().positive().optional(),
|
|
159
|
-
feature_slices: z
|
|
160
|
-
.array(z.object({
|
|
161
|
-
id: z.string().min(1),
|
|
162
|
-
repos: z.array(z.string()).optional(),
|
|
163
|
-
interfaces: z.array(z.string()).optional(),
|
|
164
|
-
pages: z.array(z.string()).optional(),
|
|
165
|
-
design_points: z.array(z.string()).optional(),
|
|
166
|
-
parts: z.array(z.string().min(1)).min(1),
|
|
167
|
-
}))
|
|
168
|
-
.optional(),
|
|
169
|
-
review: z
|
|
170
|
-
.object({
|
|
171
|
-
enabled: z.boolean().optional(),
|
|
172
|
-
required_pass: z.boolean().optional(),
|
|
173
|
-
})
|
|
174
|
-
.optional(),
|
|
175
|
-
})
|
|
176
|
-
.optional(),
|
|
177
|
-
});
|
|
178
|
-
export function parseApprovalIndex(raw) {
|
|
179
|
-
return approvalIndexSchema.parse(raw);
|
|
180
|
-
}
|
|
181
|
-
/** Returns true when segmented fast-path (monolithic) is allowed. */
|
|
182
|
-
export function isLightweightApproval(index) {
|
|
183
|
-
return (index.tables.length <= 2 &&
|
|
184
|
-
index.interfaces.length <= 3 &&
|
|
185
|
-
index.pages.length <= 2 &&
|
|
186
|
-
!index.optional.s5);
|
|
187
|
-
}
|
|
188
|
-
export function shouldForceSegmented(index) {
|
|
189
|
-
if (index.mode === 'monolithic') {
|
|
190
|
-
return false;
|
|
191
|
-
}
|
|
192
|
-
return !isLightweightApproval(index);
|
|
193
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export type { ApprovalAssembleResult, ApprovalDiagnostic, ApprovalIndex, ApprovalManifest, ApprovalGenerationMode, ApprovalMultiRepo, ApprovalDocumentMode, ApprovalPlaybook, ApprovalBundleResult, ApprovalReviewPacket, ApprovalReviewResult, ApprovalAcpBlock, ApprovalFeatureSlice, } from './types.js';
|
|
2
|
-
export { parseApprovalIndex, isLightweightApproval, shouldForceSegmented, approvalIndexSchema, } from './index-schema.js';
|
|
3
|
-
export { parseApprovalPlaybook, parseApprovalReviewResult } from './playbook-schema.js';
|
|
4
|
-
export { getChangeDirectory, getApprovalWorkspaceDir, getApprovalIndexPath, getApprovalManifestPath, getApprovalPartsDir, getApprovalPartPath, getApprovalOutputPath, getApprovalAnalysisPath, getApprovalReviewPacketPath, getApprovalReviewPath, getApprovalPlaybookPath, getApprovalBundleOutputPath, } from './paths.js';
|
|
5
|
-
export { assembleApprovalDocument, readApprovalIndex, readApprovalManifest, } from './assemble.js';
|
|
6
|
-
export { lintApprovalDocument } from './lint.js';
|
|
7
|
-
export { bundleApprovalDocument, resolvePlaybookPath } from './bundle.js';
|
|
8
|
-
export { generateReviewPacket, checkApprovalReview, computeReviewPartHashes } from './review-pack.js';
|
|
9
|
-
export { finalizeApprovalDocument } from './pipeline.js';
|
|
10
|
-
export type { FinalizeApprovalResult, FinalizeApprovalOptions, ApprovalFinalizeStage } from './pipeline.js';
|
|
11
|
-
export { renderApprovalDocument, resolveApprovalRenderPaths } from './render.js';
|
|
12
|
-
export type { RenderApprovalOptions, RenderApprovalResult } from './render.js';
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
export { parseApprovalIndex, isLightweightApproval, shouldForceSegmented, approvalIndexSchema, } from './index-schema.js';
|
|
2
|
-
export { parseApprovalPlaybook, parseApprovalReviewResult } from './playbook-schema.js';
|
|
3
|
-
export { getChangeDirectory, getApprovalWorkspaceDir, getApprovalIndexPath, getApprovalManifestPath, getApprovalPartsDir, getApprovalPartPath, getApprovalOutputPath, getApprovalAnalysisPath, getApprovalReviewPacketPath, getApprovalReviewPath, getApprovalPlaybookPath, getApprovalBundleOutputPath, } from './paths.js';
|
|
4
|
-
export { assembleApprovalDocument, readApprovalIndex, readApprovalManifest, } from './assemble.js';
|
|
5
|
-
export { lintApprovalDocument } from './lint.js';
|
|
6
|
-
export { bundleApprovalDocument, resolvePlaybookPath } from './bundle.js';
|
|
7
|
-
export { generateReviewPacket, checkApprovalReview, computeReviewPartHashes } from './review-pack.js';
|
|
8
|
-
export { finalizeApprovalDocument } from './pipeline.js';
|
|
9
|
-
export { renderApprovalDocument, resolveApprovalRenderPaths } from './render.js';
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { ApprovalDiagnostic } from './types.js';
|
|
2
|
-
export interface ApprovalLintOptions {
|
|
3
|
-
readonly changeDir: string;
|
|
4
|
-
readonly strict?: boolean;
|
|
5
|
-
}
|
|
6
|
-
export interface ApprovalLintResult {
|
|
7
|
-
readonly ok: boolean;
|
|
8
|
-
readonly diagnostics: readonly ApprovalDiagnostic[];
|
|
9
|
-
}
|
|
10
|
-
export declare function lintApprovalDocument(options: ApprovalLintOptions): Promise<ApprovalLintResult>;
|
|
@@ -1,302 +0,0 @@
|
|
|
1
|
-
import { promises as fs } from 'node:fs';
|
|
2
|
-
import { findForbiddenMatch, findInconsistencyHints } from './forbidden-patterns.js';
|
|
3
|
-
import { readApprovalIndex } from './assemble.js';
|
|
4
|
-
import { getApprovalAnalysisPath, getApprovalPartPath } from './paths.js';
|
|
5
|
-
async function readPartContents(changeDir, index) {
|
|
6
|
-
const parts = new Map();
|
|
7
|
-
for (const partId of index.parts_order) {
|
|
8
|
-
try {
|
|
9
|
-
parts.set(partId, await fs.readFile(getApprovalPartPath(changeDir, partId), 'utf-8'));
|
|
10
|
-
}
|
|
11
|
-
catch {
|
|
12
|
-
// missing parts handled by assemble check
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
return parts;
|
|
16
|
-
}
|
|
17
|
-
function allPartsText(parts) {
|
|
18
|
-
return [...parts.values()].join('\n');
|
|
19
|
-
}
|
|
20
|
-
export async function lintApprovalDocument(options) {
|
|
21
|
-
const diagnostics = [];
|
|
22
|
-
const { changeDir, strict = false } = options;
|
|
23
|
-
let index;
|
|
24
|
-
try {
|
|
25
|
-
index = await readApprovalIndex(changeDir);
|
|
26
|
-
}
|
|
27
|
-
catch (error) {
|
|
28
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
29
|
-
return {
|
|
30
|
-
ok: false,
|
|
31
|
-
diagnostics: [
|
|
32
|
-
{
|
|
33
|
-
code: 'missing_index',
|
|
34
|
-
severity: 'error',
|
|
35
|
-
message: `Cannot read approval/index.yaml: ${message}`,
|
|
36
|
-
},
|
|
37
|
-
],
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
const partContents = await readPartContents(changeDir, index);
|
|
41
|
-
const combined = allPartsText(partContents);
|
|
42
|
-
// C3: design_points referenced somewhere in parts
|
|
43
|
-
for (const point of index.design_points) {
|
|
44
|
-
const id = point.replace(/^P/, 'P');
|
|
45
|
-
const pattern = new RegExp(`\\b${escapeRegExp(id)}\\b`);
|
|
46
|
-
if (!pattern.test(combined)) {
|
|
47
|
-
diagnostics.push({
|
|
48
|
-
code: 'design_point_unreferenced',
|
|
49
|
-
severity: strict ? 'error' : 'warning',
|
|
50
|
-
message: `Design point "${id}" listed in index but not referenced in any part`,
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
// C3b: decisions referenced in parts
|
|
55
|
-
for (const decision of index.decisions) {
|
|
56
|
-
const id = decision.replace(/^D/, 'D');
|
|
57
|
-
const pattern = new RegExp(`\\b${escapeRegExp(id)}\\b`);
|
|
58
|
-
if (!pattern.test(combined)) {
|
|
59
|
-
diagnostics.push({
|
|
60
|
-
code: 'decision_unreferenced',
|
|
61
|
-
severity: strict ? 'error' : 'warning',
|
|
62
|
-
message: `Decision "${id}" listed in index but not referenced in any part`,
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
// C3c: tables / capabilities referenced in assigned parts
|
|
67
|
-
for (const table of index.tables) {
|
|
68
|
-
const partText = partContents.get(table.part) ?? '';
|
|
69
|
-
if (partText.length === 0) {
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
72
|
-
const hasRef = partText.includes(table.id) ||
|
|
73
|
-
partText.includes(table.name) ||
|
|
74
|
-
new RegExp(`\\b${escapeRegExp(table.id)}\\b`).test(partText);
|
|
75
|
-
if (!hasRef) {
|
|
76
|
-
diagnostics.push({
|
|
77
|
-
code: 'table_unreferenced',
|
|
78
|
-
severity: strict ? 'error' : 'warning',
|
|
79
|
-
message: `Table "${table.id}" (${table.name}) not referenced in part "${table.part}"`,
|
|
80
|
-
part: table.part,
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
for (const cap of index.capabilities) {
|
|
85
|
-
const partText = partContents.get(cap.part) ?? '';
|
|
86
|
-
if (partText.length === 0) {
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
|
-
if (!partText.includes(cap.id)) {
|
|
90
|
-
diagnostics.push({
|
|
91
|
-
code: 'capability_unreferenced',
|
|
92
|
-
severity: strict ? 'error' : 'warning',
|
|
93
|
-
message: `Capability "${cap.id}" not referenced in part "${cap.part}"`,
|
|
94
|
-
part: cap.part,
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
// C4: page apis exist as interface ids
|
|
99
|
-
const interfaceIds = new Set(index.interfaces.map((i) => i.id));
|
|
100
|
-
for (const page of index.pages) {
|
|
101
|
-
for (const apiId of page.apis) {
|
|
102
|
-
if (!interfaceIds.has(apiId)) {
|
|
103
|
-
diagnostics.push({
|
|
104
|
-
code: 'page_api_missing',
|
|
105
|
-
severity: 'error',
|
|
106
|
-
message: `Page "${page.id}" references api "${apiId}" not in index.interfaces`,
|
|
107
|
-
part: page.part,
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
// C1 + C2: frozen fields and pairs_with
|
|
113
|
-
for (const iface of index.interfaces) {
|
|
114
|
-
const frozen = iface.frozen;
|
|
115
|
-
const partText = partContents.get(iface.part) ?? '';
|
|
116
|
-
if (frozen?.rpc) {
|
|
117
|
-
const rpcShort = frozen.rpc.split('.').pop() ?? frozen.rpc;
|
|
118
|
-
if (partText.length > 0 && !partText.includes(frozen.rpc) && !partText.includes(rpcShort)) {
|
|
119
|
-
diagnostics.push({
|
|
120
|
-
code: 'frozen_rpc_missing',
|
|
121
|
-
severity: 'error',
|
|
122
|
-
message: `Interface "${iface.id}" frozen.rpc "${frozen.rpc}" not found in part "${iface.part}"`,
|
|
123
|
-
part: iface.part,
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
if (frozen?.http_path) {
|
|
128
|
-
const pathOnly = frozen.http_path.replace(/^(GET|POST|PUT|PATCH|DELETE)\s+/i, '');
|
|
129
|
-
if (partText.length > 0 && !partText.includes(pathOnly)) {
|
|
130
|
-
diagnostics.push({
|
|
131
|
-
code: 'frozen_http_path_missing',
|
|
132
|
-
severity: 'error',
|
|
133
|
-
message: `Interface "${iface.id}" frozen.http_path "${frozen.http_path}" not found in part "${iface.part}"`,
|
|
134
|
-
part: iface.part,
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
if (frozen?.status_enum && frozen.status_enum.length > 0) {
|
|
139
|
-
for (const status of frozen.status_enum) {
|
|
140
|
-
if (partText.length > 0 && !partText.includes(status)) {
|
|
141
|
-
diagnostics.push({
|
|
142
|
-
code: 'frozen_status_enum_missing',
|
|
143
|
-
severity: strict ? 'error' : 'warning',
|
|
144
|
-
message: `Interface "${iface.id}" frozen status "${status}" not mentioned in part "${iface.part}"`,
|
|
145
|
-
part: iface.part,
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
if (iface.pairs_with) {
|
|
151
|
-
const pair = index.interfaces.find((i) => i.id === iface.pairs_with);
|
|
152
|
-
if (!pair) {
|
|
153
|
-
diagnostics.push({
|
|
154
|
-
code: 'pairs_with_missing',
|
|
155
|
-
severity: 'error',
|
|
156
|
-
message: `Interface "${iface.id}" pairs_with "${iface.pairs_with}" not in index.interfaces`,
|
|
157
|
-
part: iface.part,
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
else if (!partContents.has(pair.part)) {
|
|
161
|
-
diagnostics.push({
|
|
162
|
-
code: 'pairs_with_part_missing',
|
|
163
|
-
severity: 'error',
|
|
164
|
-
message: `Interface "${iface.id}" pairs_with part "${pair.part}" file missing`,
|
|
165
|
-
part: iface.part,
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
else {
|
|
169
|
-
const pairText = partContents.get(pair.part) ?? '';
|
|
170
|
-
if (partText.length > 0 && !partText.includes(iface.pairs_with)) {
|
|
171
|
-
diagnostics.push({
|
|
172
|
-
code: 'pairs_with_unref',
|
|
173
|
-
severity: 'warning',
|
|
174
|
-
message: `Interface "${iface.id}" part does not cross-ref pairs_with "${iface.pairs_with}"`,
|
|
175
|
-
part: iface.part,
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
if (pairText.length > 0 && !pairText.includes(iface.id)) {
|
|
179
|
-
diagnostics.push({
|
|
180
|
-
code: 'pairs_with_unref',
|
|
181
|
-
severity: 'warning',
|
|
182
|
-
message: `Interface "${iface.pairs_with}" part does not cross-ref pair "${iface.id}"`,
|
|
183
|
-
part: pair.part,
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
// C6: feature_slices — all listed parts exist + depth parity
|
|
190
|
-
if (index.acp?.feature_slices) {
|
|
191
|
-
for (const slice of index.acp.feature_slices) {
|
|
192
|
-
const byteLengths = [];
|
|
193
|
-
for (const partId of slice.parts) {
|
|
194
|
-
const content = partContents.get(partId);
|
|
195
|
-
if (!content) {
|
|
196
|
-
diagnostics.push({
|
|
197
|
-
code: 'feature_slice_part_missing',
|
|
198
|
-
severity: 'error',
|
|
199
|
-
message: `Feature slice "${slice.id}" references missing part "${partId}"`,
|
|
200
|
-
part: partId,
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
else {
|
|
204
|
-
byteLengths.push(Buffer.byteLength(content, 'utf8'));
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
if (byteLengths.length >= 2) {
|
|
208
|
-
const min = Math.min(...byteLengths);
|
|
209
|
-
const max = Math.max(...byteLengths);
|
|
210
|
-
if (min > 0 && max / min >= 8) {
|
|
211
|
-
diagnostics.push({
|
|
212
|
-
code: 'feature_slice_depth_imbalance',
|
|
213
|
-
severity: strict ? 'error' : 'warning',
|
|
214
|
-
message: `Feature slice "${slice.id}" has large depth imbalance across parts (max/min bytes ≥ 8)`,
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
// C6b: context budget hint (acp/v2)
|
|
221
|
-
const budget = index.acp?.context_budget_tokens;
|
|
222
|
-
if (budget && budget > 0) {
|
|
223
|
-
const approxTokens = Math.ceil(combined.length / 4);
|
|
224
|
-
if (approxTokens > budget * 1.5) {
|
|
225
|
-
diagnostics.push({
|
|
226
|
-
code: 'context_budget_exceeded',
|
|
227
|
-
severity: 'warning',
|
|
228
|
-
message: `Combined parts ~${approxTokens} tokens exceed acp.context_budget_tokens (${budget}) — consider smaller Map batches`,
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
// C7: analysis.json frozen_rpc aligns with index
|
|
233
|
-
try {
|
|
234
|
-
const analysisRaw = JSON.parse(await fs.readFile(getApprovalAnalysisPath(changeDir), 'utf-8'));
|
|
235
|
-
if (analysisRaw.frozen_rpc) {
|
|
236
|
-
const rpcInterfaces = index.interfaces.filter((i) => i.frozen?.rpc);
|
|
237
|
-
if (rpcInterfaces.length > 0) {
|
|
238
|
-
const mismatch = rpcInterfaces.some((i) => i.frozen.rpc !== analysisRaw.frozen_rpc);
|
|
239
|
-
if (mismatch) {
|
|
240
|
-
diagnostics.push({
|
|
241
|
-
code: 'analysis_frozen_rpc_mismatch',
|
|
242
|
-
severity: 'warning',
|
|
243
|
-
message: `analysis.json frozen_rpc "${analysisRaw.frozen_rpc}" differs from index interface frozen.rpc values`,
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
catch {
|
|
250
|
-
// analysis optional for lint
|
|
251
|
-
}
|
|
252
|
-
// C8: multi_repo — each repo has at least one entity or part tagged
|
|
253
|
-
if (index.multi_repo?.enabled) {
|
|
254
|
-
for (const repo of index.multi_repo.repos) {
|
|
255
|
-
const hasEntity = index.interfaces.some((i) => i.repo === repo.id) ||
|
|
256
|
-
index.pages.some((p) => p.repo === repo.id) ||
|
|
257
|
-
index.tables.some((t) => t.repo === repo.id);
|
|
258
|
-
if (!hasEntity && index.multi_repo.document_mode === 'per_repo') {
|
|
259
|
-
diagnostics.push({
|
|
260
|
-
code: 'multi_repo_repo_unrepresented',
|
|
261
|
-
severity: 'warning',
|
|
262
|
-
message: `Multi-repo entry "${repo.id}" has no interfaces/pages/tables with repo tag`,
|
|
263
|
-
});
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
// C5: forbidden patterns per part
|
|
268
|
-
for (const [partId, content] of partContents) {
|
|
269
|
-
const forbidden = findForbiddenMatch(content);
|
|
270
|
-
if (forbidden) {
|
|
271
|
-
diagnostics.push({
|
|
272
|
-
code: 'forbidden_pattern',
|
|
273
|
-
severity: 'error',
|
|
274
|
-
message: `Part "${partId}": forbidden stub pattern (${forbidden.source})`,
|
|
275
|
-
part: partId,
|
|
276
|
-
});
|
|
277
|
-
}
|
|
278
|
-
for (const hint of findInconsistencyHints(content)) {
|
|
279
|
-
diagnostics.push({
|
|
280
|
-
code: 'inconsistency_hint',
|
|
281
|
-
severity: strict ? 'error' : 'warning',
|
|
282
|
-
message: `Part "${partId}": possible semantic inconsistency (${hint.source})`,
|
|
283
|
-
part: partId,
|
|
284
|
-
});
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
// C9: appendix duplication in signoff
|
|
288
|
-
const signoff = partContents.get('10-signoff') ?? '';
|
|
289
|
-
if (/附录\s*A/i.test(signoff)) {
|
|
290
|
-
diagnostics.push({
|
|
291
|
-
code: 'appendix_in_signoff',
|
|
292
|
-
severity: 'warning',
|
|
293
|
-
message: 'Part "10-signoff" contains 附录 A; assemble will skip auto-appendix — ensure no duplicate in output',
|
|
294
|
-
part: '10-signoff',
|
|
295
|
-
});
|
|
296
|
-
}
|
|
297
|
-
const hasError = diagnostics.some((d) => d.severity === 'error');
|
|
298
|
-
return { ok: !hasError, diagnostics };
|
|
299
|
-
}
|
|
300
|
-
function escapeRegExp(value) {
|
|
301
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
302
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
export declare function getChangeDirectory(projectRoot: string, changeName: string): string;
|
|
2
|
-
export declare function getApprovalWorkspaceDir(changeDir: string): string;
|
|
3
|
-
export declare function getApprovalIndexPath(changeDir: string): string;
|
|
4
|
-
export declare function getApprovalManifestPath(changeDir: string): string;
|
|
5
|
-
export declare function getApprovalPartsDir(changeDir: string): string;
|
|
6
|
-
export declare function getApprovalPartPath(changeDir: string, partId: string): string;
|
|
7
|
-
export declare function getApprovalOutputPath(changeDir: string): string;
|
|
8
|
-
export declare function getApprovalAnalysisPath(changeDir: string): string;
|
|
9
|
-
export declare function getApprovalReviewPacketPath(changeDir: string): string;
|
|
10
|
-
export declare function getApprovalReviewPath(changeDir: string): string;
|
|
11
|
-
export declare function getApprovalPlaybookPath(changeDir: string): string;
|
|
12
|
-
export declare function getApprovalBundleOutputPath(changeDir: string, relativePath: string): string;
|
|
13
|
-
export declare function getApprovalHtmlOutputPath(changeDir: string): string;
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { join } from 'node:path';
|
|
2
|
-
import { validateChangeName } from '../../utils/change-utils.js';
|
|
3
|
-
const CHANGES_REL = 'specflow/changes';
|
|
4
|
-
export function getChangeDirectory(projectRoot, changeName) {
|
|
5
|
-
validateChangeName(changeName);
|
|
6
|
-
return join(projectRoot, CHANGES_REL, changeName);
|
|
7
|
-
}
|
|
8
|
-
export function getApprovalWorkspaceDir(changeDir) {
|
|
9
|
-
return join(changeDir, 'approval');
|
|
10
|
-
}
|
|
11
|
-
export function getApprovalIndexPath(changeDir) {
|
|
12
|
-
return join(getApprovalWorkspaceDir(changeDir), 'index.yaml');
|
|
13
|
-
}
|
|
14
|
-
export function getApprovalManifestPath(changeDir) {
|
|
15
|
-
return join(getApprovalWorkspaceDir(changeDir), 'manifest.json');
|
|
16
|
-
}
|
|
17
|
-
export function getApprovalPartsDir(changeDir) {
|
|
18
|
-
return join(getApprovalWorkspaceDir(changeDir), 'parts');
|
|
19
|
-
}
|
|
20
|
-
export function getApprovalPartPath(changeDir, partId) {
|
|
21
|
-
return join(getApprovalPartsDir(changeDir), `${partId}.md`);
|
|
22
|
-
}
|
|
23
|
-
export function getApprovalOutputPath(changeDir) {
|
|
24
|
-
return join(changeDir, 'approval.md');
|
|
25
|
-
}
|
|
26
|
-
export function getApprovalAnalysisPath(changeDir) {
|
|
27
|
-
return join(getApprovalWorkspaceDir(changeDir), 'analysis.json');
|
|
28
|
-
}
|
|
29
|
-
export function getApprovalReviewPacketPath(changeDir) {
|
|
30
|
-
return join(getApprovalWorkspaceDir(changeDir), 'review-packet.json');
|
|
31
|
-
}
|
|
32
|
-
export function getApprovalReviewPath(changeDir) {
|
|
33
|
-
return join(getApprovalWorkspaceDir(changeDir), 'review-result.json');
|
|
34
|
-
}
|
|
35
|
-
export function getApprovalPlaybookPath(changeDir) {
|
|
36
|
-
return join(getApprovalWorkspaceDir(changeDir), 'approval-playbook.yaml');
|
|
37
|
-
}
|
|
38
|
-
export function getApprovalBundleOutputPath(changeDir, relativePath) {
|
|
39
|
-
return join(changeDir, relativePath);
|
|
40
|
-
}
|
|
41
|
-
export function getApprovalHtmlOutputPath(changeDir) {
|
|
42
|
-
return join(changeDir, 'approval.html');
|
|
43
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import type { ApprovalDiagnostic } from './types.js';
|
|
2
|
-
export type ApprovalFinalizeStage = 'lint' | 'review-pack' | 'review-required' | 'review-check' | 'check' | 'assemble' | 'bundle' | 'done';
|
|
3
|
-
export interface FinalizeApprovalOptions {
|
|
4
|
-
readonly changeDir: string;
|
|
5
|
-
readonly workspaceRoot?: string;
|
|
6
|
-
readonly strict?: boolean;
|
|
7
|
-
readonly skipReview?: boolean;
|
|
8
|
-
readonly force?: boolean;
|
|
9
|
-
readonly bundle?: boolean | 'auto';
|
|
10
|
-
readonly playbook?: string;
|
|
11
|
-
readonly dryRun?: boolean;
|
|
12
|
-
readonly validateLazy?: boolean;
|
|
13
|
-
}
|
|
14
|
-
export interface FinalizeApprovalResult {
|
|
15
|
-
readonly ok: boolean;
|
|
16
|
-
readonly paused?: boolean;
|
|
17
|
-
readonly stage: ApprovalFinalizeStage;
|
|
18
|
-
readonly change: string;
|
|
19
|
-
readonly outputPath?: string;
|
|
20
|
-
readonly bundlePath?: string;
|
|
21
|
-
readonly reviewPacketPath?: string;
|
|
22
|
-
readonly diagnostics: readonly ApprovalDiagnostic[];
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* One-shot ACP finalize: lint → review gate → check → assemble → optional bundle.
|
|
26
|
-
* When AI review is required but review-result.json is missing, pauses after review-pack.
|
|
27
|
-
*/
|
|
28
|
-
export declare function finalizeApprovalDocument(options: FinalizeApprovalOptions): Promise<FinalizeApprovalResult>;
|