@gordon.gan/specflow 1.5.0-beta → 1.7.0-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.
Files changed (39) hide show
  1. package/README.md +2 -2
  2. package/dist/cli/commands/approval-assemble.d.ts +48 -5
  3. package/dist/cli/commands/approval-assemble.js +347 -34
  4. package/dist/core/approval/assemble.js +15 -22
  5. package/dist/core/approval/bundle.d.ts +9 -0
  6. package/dist/core/approval/bundle.js +172 -0
  7. package/dist/core/approval/forbidden-patterns.d.ts +6 -0
  8. package/dist/core/approval/forbidden-patterns.js +37 -0
  9. package/dist/core/approval/index-schema.d.ts +203 -0
  10. package/dist/core/approval/index-schema.js +47 -0
  11. package/dist/core/approval/index.d.ts +10 -2
  12. package/dist/core/approval/index.js +7 -1
  13. package/dist/core/approval/lint.d.ts +10 -0
  14. package/dist/core/approval/lint.js +302 -0
  15. package/dist/core/approval/paths.d.ts +5 -0
  16. package/dist/core/approval/paths.js +15 -0
  17. package/dist/core/approval/pipeline.d.ts +28 -0
  18. package/dist/core/approval/pipeline.js +146 -0
  19. package/dist/core/approval/playbook-schema.d.ts +182 -0
  20. package/dist/core/approval/playbook-schema.js +51 -0
  21. package/dist/core/approval/render.d.ts +20 -0
  22. package/dist/core/approval/render.js +210 -0
  23. package/dist/core/approval/review-pack.d.ts +26 -0
  24. package/dist/core/approval/review-pack.js +205 -0
  25. package/dist/core/approval/types.d.ts +103 -0
  26. package/dist/integrations/shared/capability-evidence.js +2 -0
  27. package/dist/integrations/shared/parity-manifest.js +2 -0
  28. package/package.json +2 -1
  29. package/prompts/approval/acp-pipeline.md +104 -0
  30. package/prompts/approval/ai-review.md +145 -0
  31. package/prompts/approval/generate.md +94 -9
  32. package/prompts/approval/multi-repo-guidance.md +46 -10
  33. package/prompts/approval/runtime-guidance.md +64 -0
  34. package/prompts/approval/segmented-generation.md +14 -8
  35. package/skills/GUIDANCE_PACKS.md +1 -1
  36. package/skills/specflow-approval/SKILL.md +48 -25
  37. package/templates/approval-index.yaml +25 -4
  38. package/templates/approval-part.md +1 -1
  39. package/templates/approval-playbook.yaml +28 -0
@@ -0,0 +1,172 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { promises as fs } from 'node:fs';
3
+ import { join, dirname } from 'node:path';
4
+ import yaml from 'js-yaml';
5
+ import { parseApprovalPlaybook } from './playbook-schema.js';
6
+ import { readApprovalIndex } from './assemble.js';
7
+ import { getApprovalPartPath, getApprovalWorkspaceDir, getChangeDirectory, } from './paths.js';
8
+ function sha256(content) {
9
+ return createHash('sha256').update(content, 'utf8').digest('hex');
10
+ }
11
+ function resolveSourceChangeDir(workspaceRoot, source) {
12
+ const planningRoot = source.root ?? workspaceRoot;
13
+ return getChangeDirectory(planningRoot, source.change);
14
+ }
15
+ async function loadSourceParts(changeDir, includeParts) {
16
+ const index = await readApprovalIndex(changeDir);
17
+ const partIds = includeParts === 'all'
18
+ ? [...index.parts_order]
19
+ : includeParts.filter((id) => index.parts_order.includes(id));
20
+ const results = [];
21
+ for (const partId of partIds) {
22
+ try {
23
+ const content = await fs.readFile(getApprovalPartPath(changeDir, partId), 'utf-8');
24
+ results.push({ partId, content });
25
+ }
26
+ catch {
27
+ // skip missing
28
+ }
29
+ }
30
+ return results;
31
+ }
32
+ export async function bundleApprovalDocument(options) {
33
+ const diagnostics = [];
34
+ const { workspaceRoot, changeDir, playbookPath, write = true } = options;
35
+ let playbook;
36
+ let indexOutputOverride;
37
+ try {
38
+ const raw = yaml.load(await fs.readFile(playbookPath, 'utf-8'));
39
+ playbook = parseApprovalPlaybook(raw);
40
+ try {
41
+ const index = await readApprovalIndex(changeDir);
42
+ indexOutputOverride = index.multi_repo?.bundle?.output;
43
+ }
44
+ catch {
45
+ // index optional for bundle-only dry runs
46
+ }
47
+ }
48
+ catch (error) {
49
+ const message = error instanceof Error ? error.message : String(error);
50
+ return {
51
+ ok: false,
52
+ diagnostics: [
53
+ { code: 'invalid_playbook', severity: 'error', message: `Invalid playbook: ${message}` },
54
+ ],
55
+ };
56
+ }
57
+ const body = [];
58
+ body.push(`# ${playbook.title}`);
59
+ body.push('');
60
+ body.push(`> **合订视图** (read-only bundle) | change: \`${playbook.change}\``);
61
+ body.push(`> 生成方式: \`specflow approval bundle\` — 确定性拼接,禁止 LLM Reduce`);
62
+ body.push(`> 主仓: \`${playbook.primary_repo}\``);
63
+ body.push('');
64
+ body.push('---');
65
+ body.push('');
66
+ if (playbook.overlay && playbook.overlay.length > 0) {
67
+ const approvalDir = getApprovalWorkspaceDir(changeDir);
68
+ for (const overlayRel of playbook.overlay) {
69
+ const overlayPath = join(approvalDir, overlayRel);
70
+ try {
71
+ const content = await fs.readFile(overlayPath, 'utf-8');
72
+ if (playbook.assemble.inject_headers !== false) {
73
+ body.push(`## Overlay · ${overlayRel}`);
74
+ body.push('');
75
+ }
76
+ body.push(content.trim());
77
+ body.push('');
78
+ body.push('---');
79
+ body.push('');
80
+ }
81
+ catch {
82
+ diagnostics.push({
83
+ code: 'overlay_missing',
84
+ severity: 'error',
85
+ message: `Overlay file not found: ${overlayPath}`,
86
+ });
87
+ }
88
+ }
89
+ }
90
+ for (const source of playbook.sources) {
91
+ const sourceChangeDir = resolveSourceChangeDir(workspaceRoot, source);
92
+ try {
93
+ await fs.access(sourceChangeDir);
94
+ }
95
+ catch {
96
+ diagnostics.push({
97
+ code: 'source_change_missing',
98
+ severity: 'error',
99
+ message: `Source change not found: ${sourceChangeDir} (repo=${source.repo})`,
100
+ });
101
+ continue;
102
+ }
103
+ const includeParts = source.include_parts ?? 'all';
104
+ const parts = await loadSourceParts(sourceChangeDir, includeParts);
105
+ if (parts.length === 0) {
106
+ diagnostics.push({
107
+ code: 'source_parts_empty',
108
+ severity: 'warning',
109
+ message: `No parts loaded for source ${source.repo}/${source.change}`,
110
+ });
111
+ }
112
+ if (playbook.assemble.inject_headers !== false) {
113
+ body.push(`## 来源 · ${source.repo} (\`${source.change}\`)`);
114
+ body.push('');
115
+ }
116
+ for (const { partId, content } of parts) {
117
+ body.push(`<!-- bundle-source: ${source.repo}/${source.change}/${partId} -->`);
118
+ body.push(content.trim());
119
+ body.push('');
120
+ }
121
+ body.push('---');
122
+ body.push('');
123
+ }
124
+ if (diagnostics.some((d) => d.severity === 'error')) {
125
+ return { ok: false, diagnostics };
126
+ }
127
+ let markdown = body.join('\n').replace(/\n{3,}/g, '\n\n').trim() + '\n';
128
+ if (playbook.assemble.dedupe_appendix !== false) {
129
+ const appendixMatches = markdown.match(/^##\s+附录\s*A/mg);
130
+ if (appendixMatches && appendixMatches.length > 1) {
131
+ diagnostics.push({
132
+ code: 'duplicate_appendix',
133
+ severity: 'warning',
134
+ message: `Bundle output contains ${appendixMatches.length} "## 附录 A" headings`,
135
+ });
136
+ }
137
+ }
138
+ if (playbook.assemble.readonly) {
139
+ markdown =
140
+ `> **READ-ONLY VIEW** — apply 请使用各仓 \`approval.md\` 真源,勿直接消费本合订文件。\n\n` +
141
+ markdown;
142
+ }
143
+ const outputRel = indexOutputOverride ?? playbook.assemble.output;
144
+ const outputPath = join(changeDir, outputRel);
145
+ if (!write) {
146
+ return { ok: true, markdown, outputPath, diagnostics };
147
+ }
148
+ await fs.mkdir(dirname(outputPath), { recursive: true });
149
+ await fs.writeFile(outputPath, markdown, 'utf-8');
150
+ const bundleManifestPath = join(getApprovalWorkspaceDir(changeDir), 'bundle-manifest.json');
151
+ await fs.writeFile(bundleManifestPath, JSON.stringify({
152
+ schema: 'specflow.approval.bundle-manifest/v1',
153
+ change: playbook.change,
154
+ playbook: playbookPath,
155
+ output: outputRel,
156
+ sha256: sha256(markdown),
157
+ assembled_at: new Date().toISOString(),
158
+ sources: playbook.sources.map((s) => ({ repo: s.repo, change: s.change })),
159
+ }, null, 2), 'utf-8');
160
+ return { ok: true, markdown, outputPath, diagnostics };
161
+ }
162
+ export async function resolvePlaybookPath(changeDir, explicitPath) {
163
+ if (explicitPath) {
164
+ return explicitPath;
165
+ }
166
+ const index = await readApprovalIndex(changeDir);
167
+ const fromIndex = index.multi_repo?.bundle?.playbook;
168
+ if (fromIndex) {
169
+ return join(getApprovalWorkspaceDir(changeDir), fromIndex);
170
+ }
171
+ return join(getApprovalWorkspaceDir(changeDir), 'approval-playbook.yaml');
172
+ }
@@ -0,0 +1,6 @@
1
+ /** Shared stub / inconsistency patterns for assemble lazy-check and approval lint. */
2
+ export declare const FORBIDDEN_STUB_PATTERNS: readonly RegExp[];
3
+ /** Semantic inconsistency hints (warnings unless strict mode) — long-document generic. */
4
+ export declare const INCONSISTENCY_HINT_PATTERNS: readonly RegExp[];
5
+ export declare function findForbiddenMatch(content: string): RegExp | null;
6
+ export declare function findInconsistencyHints(content: string): readonly RegExp[];
@@ -0,0 +1,37 @@
1
+ /** Shared stub / inconsistency patterns for assemble lazy-check and approval lint. */
2
+ export const FORBIDDEN_STUB_PATTERNS = [
3
+ /\bTODO\b/i,
4
+ /待补充/,
5
+ /待 refine 澄清(?!.*\[)/,
6
+ /此处省略/,
7
+ /详见\s*(?:上文|下文|附件)(?!.*§)/,
8
+ /略\s*[。.]?$/,
9
+ /TBD\s*[。.]?$/,
10
+ /(?:^|\n)\s*\.{3}\s*(?:\n|$)/,
11
+ /(?:^|\n)\s*(?:同上|同前)\s*[。.]?\s*(?:\n|$)/,
12
+ /实现时(?:命名|对齐)/,
13
+ /(?:RPC|rpc).*暂定/,
14
+ /暂定\s*[`']?\w+[`']?/,
15
+ /\b如\s+[A-Z]\w*(?:Result|Request|Response)/,
16
+ /内部(?:经|调用)\s*I\d+[^.\n]{0,40}(?:一行|代替|省略)/,
17
+ ];
18
+ /** Semantic inconsistency hints (warnings unless strict mode) — long-document generic. */
19
+ export const INCONSISTENCY_HINT_PATTERNS = [
20
+ /约\s*\d+\s*次/,
21
+ /\b(?:等|etc\.?)\s*[。.)]/,
22
+ /(?:status|状态|enum|枚举)[^.\n]{0,30}(?:等|etc)/i,
23
+ /\b(?:TBD|待定|未定)\b/i,
24
+ /(?:同上|同前|见上)(?!.*§)/,
25
+ /\d+\s*[或\\/]\s*\d+\s*(?:均可|都行)/,
26
+ ];
27
+ export function findForbiddenMatch(content) {
28
+ for (const pattern of FORBIDDEN_STUB_PATTERNS) {
29
+ if (pattern.test(content)) {
30
+ return pattern;
31
+ }
32
+ }
33
+ return null;
34
+ }
35
+ export function findInconsistencyHints(content) {
36
+ return INCONSISTENCY_HINT_PATTERNS.filter((p) => p.test(content));
37
+ }
@@ -49,18 +49,50 @@ export declare const approvalIndexSchema: z.ZodObject<{
49
49
  change: z.ZodString;
50
50
  part: z.ZodString;
51
51
  repo: z.ZodOptional<z.ZodString>;
52
+ layers: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
53
+ pairs_with: z.ZodOptional<z.ZodString>;
54
+ feature_slice: z.ZodOptional<z.ZodString>;
55
+ frozen: z.ZodOptional<z.ZodObject<{
56
+ rpc: z.ZodOptional<z.ZodString>;
57
+ http_path: z.ZodOptional<z.ZodString>;
58
+ status_enum: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
59
+ }, "strip", z.ZodTypeAny, {
60
+ rpc?: string | undefined;
61
+ http_path?: string | undefined;
62
+ status_enum?: string[] | undefined;
63
+ }, {
64
+ rpc?: string | undefined;
65
+ http_path?: string | undefined;
66
+ status_enum?: string[] | undefined;
67
+ }>>;
52
68
  }, "strip", z.ZodTypeAny, {
53
69
  id: string;
54
70
  part: string;
55
71
  short: string;
56
72
  change: string;
57
73
  repo?: string | undefined;
74
+ layers?: string[] | undefined;
75
+ pairs_with?: string | undefined;
76
+ feature_slice?: string | undefined;
77
+ frozen?: {
78
+ rpc?: string | undefined;
79
+ http_path?: string | undefined;
80
+ status_enum?: string[] | undefined;
81
+ } | undefined;
58
82
  }, {
59
83
  id: string;
60
84
  part: string;
61
85
  short: string;
62
86
  change: string;
63
87
  repo?: string | undefined;
88
+ layers?: string[] | undefined;
89
+ pairs_with?: string | undefined;
90
+ feature_slice?: string | undefined;
91
+ frozen?: {
92
+ rpc?: string | undefined;
93
+ http_path?: string | undefined;
94
+ status_enum?: string[] | undefined;
95
+ } | undefined;
64
96
  }>, "many">>;
65
97
  pages: z.ZodDefault<z.ZodArray<z.ZodObject<{
66
98
  id: z.ZodString;
@@ -209,6 +241,22 @@ export declare const approvalIndexSchema: z.ZodObject<{
209
241
  change: string;
210
242
  }[] | undefined;
211
243
  }>>;
244
+ bundle: z.ZodOptional<z.ZodObject<{
245
+ enabled: z.ZodOptional<z.ZodBoolean>;
246
+ playbook: z.ZodOptional<z.ZodString>;
247
+ output: z.ZodOptional<z.ZodString>;
248
+ readonly: z.ZodOptional<z.ZodBoolean>;
249
+ }, "strip", z.ZodTypeAny, {
250
+ output?: string | undefined;
251
+ enabled?: boolean | undefined;
252
+ playbook?: string | undefined;
253
+ readonly?: boolean | undefined;
254
+ }, {
255
+ output?: string | undefined;
256
+ enabled?: boolean | undefined;
257
+ playbook?: string | undefined;
258
+ readonly?: boolean | undefined;
259
+ }>>;
212
260
  }, "strip", z.ZodTypeAny, {
213
261
  enabled: boolean;
214
262
  repos: {
@@ -232,6 +280,12 @@ export declare const approvalIndexSchema: z.ZodObject<{
232
280
  change: string;
233
281
  }[] | undefined;
234
282
  } | undefined;
283
+ bundle?: {
284
+ output?: string | undefined;
285
+ enabled?: boolean | undefined;
286
+ playbook?: string | undefined;
287
+ readonly?: boolean | undefined;
288
+ } | undefined;
235
289
  }, {
236
290
  enabled: boolean;
237
291
  repos: {
@@ -255,6 +309,12 @@ export declare const approvalIndexSchema: z.ZodObject<{
255
309
  change: string;
256
310
  }[] | undefined;
257
311
  } | undefined;
312
+ bundle?: {
313
+ output?: string | undefined;
314
+ enabled?: boolean | undefined;
315
+ playbook?: string | undefined;
316
+ readonly?: boolean | undefined;
317
+ } | undefined;
258
318
  }>, {
259
319
  enabled: boolean;
260
320
  repos: {
@@ -278,6 +338,12 @@ export declare const approvalIndexSchema: z.ZodObject<{
278
338
  change: string;
279
339
  }[] | undefined;
280
340
  } | undefined;
341
+ bundle?: {
342
+ output?: string | undefined;
343
+ enabled?: boolean | undefined;
344
+ playbook?: string | undefined;
345
+ readonly?: boolean | undefined;
346
+ } | undefined;
281
347
  }, {
282
348
  enabled: boolean;
283
349
  repos: {
@@ -301,6 +367,81 @@ export declare const approvalIndexSchema: z.ZodObject<{
301
367
  change: string;
302
368
  }[] | undefined;
303
369
  } | undefined;
370
+ bundle?: {
371
+ output?: string | undefined;
372
+ enabled?: boolean | undefined;
373
+ playbook?: string | undefined;
374
+ readonly?: boolean | undefined;
375
+ } | undefined;
376
+ }>>;
377
+ acp: z.ZodOptional<z.ZodObject<{
378
+ pipeline: z.ZodOptional<z.ZodEnum<["acp/v1", "acp/v2"]>>;
379
+ context_budget_tokens: z.ZodOptional<z.ZodNumber>;
380
+ output_budget_tokens: z.ZodOptional<z.ZodNumber>;
381
+ feature_slices: z.ZodOptional<z.ZodArray<z.ZodObject<{
382
+ id: z.ZodString;
383
+ repos: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
384
+ interfaces: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
385
+ pages: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
386
+ design_points: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
387
+ parts: z.ZodArray<z.ZodString, "many">;
388
+ }, "strip", z.ZodTypeAny, {
389
+ id: string;
390
+ parts: string[];
391
+ repos?: string[] | undefined;
392
+ design_points?: string[] | undefined;
393
+ interfaces?: string[] | undefined;
394
+ pages?: string[] | undefined;
395
+ }, {
396
+ id: string;
397
+ parts: string[];
398
+ repos?: string[] | undefined;
399
+ design_points?: string[] | undefined;
400
+ interfaces?: string[] | undefined;
401
+ pages?: string[] | undefined;
402
+ }>, "many">>;
403
+ review: z.ZodOptional<z.ZodObject<{
404
+ enabled: z.ZodOptional<z.ZodBoolean>;
405
+ required_pass: z.ZodOptional<z.ZodBoolean>;
406
+ }, "strip", z.ZodTypeAny, {
407
+ enabled?: boolean | undefined;
408
+ required_pass?: boolean | undefined;
409
+ }, {
410
+ enabled?: boolean | undefined;
411
+ required_pass?: boolean | undefined;
412
+ }>>;
413
+ }, "strip", z.ZodTypeAny, {
414
+ review?: {
415
+ enabled?: boolean | undefined;
416
+ required_pass?: boolean | undefined;
417
+ } | undefined;
418
+ pipeline?: "acp/v1" | "acp/v2" | undefined;
419
+ context_budget_tokens?: number | undefined;
420
+ output_budget_tokens?: number | undefined;
421
+ feature_slices?: {
422
+ id: string;
423
+ parts: string[];
424
+ repos?: string[] | undefined;
425
+ design_points?: string[] | undefined;
426
+ interfaces?: string[] | undefined;
427
+ pages?: string[] | undefined;
428
+ }[] | undefined;
429
+ }, {
430
+ review?: {
431
+ enabled?: boolean | undefined;
432
+ required_pass?: boolean | undefined;
433
+ } | undefined;
434
+ pipeline?: "acp/v1" | "acp/v2" | undefined;
435
+ context_budget_tokens?: number | undefined;
436
+ output_budget_tokens?: number | undefined;
437
+ feature_slices?: {
438
+ id: string;
439
+ parts: string[];
440
+ repos?: string[] | undefined;
441
+ design_points?: string[] | undefined;
442
+ interfaces?: string[] | undefined;
443
+ pages?: string[] | undefined;
444
+ }[] | undefined;
304
445
  }>>;
305
446
  }, "strip", z.ZodTypeAny, {
306
447
  schema: "specflow.approval.index/v1";
@@ -337,6 +478,14 @@ export declare const approvalIndexSchema: z.ZodObject<{
337
478
  short: string;
338
479
  change: string;
339
480
  repo?: string | undefined;
481
+ layers?: string[] | undefined;
482
+ pairs_with?: string | undefined;
483
+ feature_slice?: string | undefined;
484
+ frozen?: {
485
+ rpc?: string | undefined;
486
+ http_path?: string | undefined;
487
+ status_enum?: string[] | undefined;
488
+ } | undefined;
340
489
  }[];
341
490
  pages: {
342
491
  id: string;
@@ -383,6 +532,29 @@ export declare const approvalIndexSchema: z.ZodObject<{
383
532
  change: string;
384
533
  }[] | undefined;
385
534
  } | undefined;
535
+ bundle?: {
536
+ output?: string | undefined;
537
+ enabled?: boolean | undefined;
538
+ playbook?: string | undefined;
539
+ readonly?: boolean | undefined;
540
+ } | undefined;
541
+ } | undefined;
542
+ acp?: {
543
+ review?: {
544
+ enabled?: boolean | undefined;
545
+ required_pass?: boolean | undefined;
546
+ } | undefined;
547
+ pipeline?: "acp/v1" | "acp/v2" | undefined;
548
+ context_budget_tokens?: number | undefined;
549
+ output_budget_tokens?: number | undefined;
550
+ feature_slices?: {
551
+ id: string;
552
+ parts: string[];
553
+ repos?: string[] | undefined;
554
+ design_points?: string[] | undefined;
555
+ interfaces?: string[] | undefined;
556
+ pages?: string[] | undefined;
557
+ }[] | undefined;
386
558
  } | undefined;
387
559
  }, {
388
560
  schema: "specflow.approval.index/v1";
@@ -419,6 +591,14 @@ export declare const approvalIndexSchema: z.ZodObject<{
419
591
  short: string;
420
592
  change: string;
421
593
  repo?: string | undefined;
594
+ layers?: string[] | undefined;
595
+ pairs_with?: string | undefined;
596
+ feature_slice?: string | undefined;
597
+ frozen?: {
598
+ rpc?: string | undefined;
599
+ http_path?: string | undefined;
600
+ status_enum?: string[] | undefined;
601
+ } | undefined;
422
602
  }[] | undefined;
423
603
  pages?: {
424
604
  id: string;
@@ -465,6 +645,29 @@ export declare const approvalIndexSchema: z.ZodObject<{
465
645
  change: string;
466
646
  }[] | undefined;
467
647
  } | undefined;
648
+ bundle?: {
649
+ output?: string | undefined;
650
+ enabled?: boolean | undefined;
651
+ playbook?: string | undefined;
652
+ readonly?: boolean | undefined;
653
+ } | undefined;
654
+ } | undefined;
655
+ acp?: {
656
+ review?: {
657
+ enabled?: boolean | undefined;
658
+ required_pass?: boolean | undefined;
659
+ } | undefined;
660
+ pipeline?: "acp/v1" | "acp/v2" | undefined;
661
+ context_budget_tokens?: number | undefined;
662
+ output_budget_tokens?: number | undefined;
663
+ feature_slices?: {
664
+ id: string;
665
+ parts: string[];
666
+ repos?: string[] | undefined;
667
+ design_points?: string[] | undefined;
668
+ interfaces?: string[] | undefined;
669
+ pages?: string[] | undefined;
670
+ }[] | undefined;
468
671
  } | undefined;
469
672
  }>;
470
673
  export declare function parseApprovalIndex(raw: unknown): ApprovalIndex;
@@ -6,12 +6,21 @@ const tableRefSchema = z.object({
6
6
  part: z.string().min(1),
7
7
  repo: z.string().min(1).optional(),
8
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
+ });
9
14
  const interfaceRefSchema = z.object({
10
15
  id: z.string().min(1),
11
16
  short: z.string().min(1),
12
17
  change: z.string().min(1),
13
18
  part: z.string().min(1),
14
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(),
15
24
  });
16
25
  const pageRefSchema = z.object({
17
26
  id: z.string().min(1),
@@ -49,6 +58,14 @@ const multiRepoSchema = z
49
58
  per_repo: z.array(outputRefSchema).optional(),
50
59
  })
51
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(),
52
69
  })
53
70
  .superRefine((value, ctx) => {
54
71
  if (!value.enabled) {
@@ -75,6 +92,13 @@ const multiRepoSchema = z
75
92
  path: ['primary_repo'],
76
93
  });
77
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
+ }
78
102
  });
79
103
  export const approvalIndexSchema = z.object({
80
104
  schema: z.literal('specflow.approval.index/v1'),
@@ -127,6 +151,29 @@ export const approvalIndexSchema = z.object({
127
151
  })
128
152
  .optional(),
129
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(),
130
177
  });
131
178
  export function parseApprovalIndex(raw) {
132
179
  return approvalIndexSchema.parse(raw);
@@ -1,4 +1,12 @@
1
- export type { ApprovalAssembleResult, ApprovalDiagnostic, ApprovalIndex, ApprovalManifest, ApprovalGenerationMode, ApprovalMultiRepo, ApprovalDocumentMode, } from './types.js';
1
+ export type { ApprovalAssembleResult, ApprovalDiagnostic, ApprovalIndex, ApprovalManifest, ApprovalGenerationMode, ApprovalMultiRepo, ApprovalDocumentMode, ApprovalPlaybook, ApprovalBundleResult, ApprovalReviewPacket, ApprovalReviewResult, ApprovalAcpBlock, ApprovalFeatureSlice, } from './types.js';
2
2
  export { parseApprovalIndex, isLightweightApproval, shouldForceSegmented, approvalIndexSchema, } from './index-schema.js';
3
- export { getChangeDirectory, getApprovalWorkspaceDir, getApprovalIndexPath, getApprovalManifestPath, getApprovalPartsDir, getApprovalPartPath, getApprovalOutputPath, getApprovalAnalysisPath, } from './paths.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';
4
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,3 +1,9 @@
1
1
  export { parseApprovalIndex, isLightweightApproval, shouldForceSegmented, approvalIndexSchema, } from './index-schema.js';
2
- export { getChangeDirectory, getApprovalWorkspaceDir, getApprovalIndexPath, getApprovalManifestPath, getApprovalPartsDir, getApprovalPartPath, getApprovalOutputPath, getApprovalAnalysisPath, } from './paths.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';
3
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';
@@ -0,0 +1,10 @@
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>;