@haposoft/cafekit 0.18.0 → 0.18.1
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 +1 -1
- package/bin/__tests__/codex-hooks.test.js +169 -236
- package/bin/__tests__/codex-native.test.js +54 -114
- package/bin/__tests__/develop-contract.test.js +101 -1361
- package/bin/__tests__/legacy-authority-decoupled.test.js +14 -19
- package/bin/__tests__/package-inventory.test.js +1 -395
- package/bin/__tests__/spec-narrowing.test.js +30 -134
- package/bin/__tests__/specs-v2-execution-closeout.test.js +0 -372
- package/bin/__tests__/test-runtime-dependency-closure.cjs +1 -2
- package/bin/__tests__/validator-grounding.test.js +17 -2929
- package/bin/lib/managed-writer.js +2 -3
- package/bin/phases/codex-runtime.js +6 -3
- package/bin/phases/copy-payload.js +13 -28
- package/bin/phases/omp-runtime.js +6 -3
- package/package.json +2 -3
- package/src/claude/agents/code-auditor.md +9 -6
- package/src/claude/agents/debugger.md +3 -9
- package/src/claude/agents/docs-keeper.md +4 -7
- package/src/claude/agents/git-ops.md +2 -4
- package/src/claude/agents/implementer.md +6 -7
- package/src/claude/agents/inspector.md +2 -0
- package/src/claude/agents/project-manager.md +2 -2
- package/src/claude/agents/spec-maker.md +4 -4
- package/src/claude/agents/test-runner.md +8 -11
- package/src/claude/agents/ui-ux-designer.md +0 -1
- package/src/claude/hooks/__tests__/spec-gate.test.js +170 -330
- package/src/claude/hooks/__tests__/spec-state-touched.test.js +26 -0
- package/src/claude/hooks/spec-gate.cjs +8 -63
- package/src/claude/hooks/spec-state.cjs +47 -38
- package/src/claude/migration-manifest.json +12 -7
- package/src/claude/output-styles/coding-level-0-eli5.md +9 -9
- package/src/claude/output-styles/coding-level-1-junior.md +3 -3
- package/src/claude/output-styles/coding-level-2-mid.md +3 -3
- package/src/claude/output-styles/coding-level-3-senior.md +2 -2
- package/src/claude/output-styles/coding-level-4-lead.md +5 -5
- package/src/claude/output-styles/coding-level-5-god.md +6 -6
- package/src/claude/rules/ai-dev-rules.md +1 -2
- package/src/claude/rules/workflow.md +1 -1
- package/src/claude/scripts/spec-receipt.cjs +0 -114
- package/src/claude/scripts/spec-resolver.cjs +41 -457
- package/src/claude/scripts/workflow-policy.cjs +3 -1801
- package/src/claude/skills/agent-browser/SKILL.md +1 -1
- package/src/claude/skills/chrome-devtools/SKILL.md +14 -31
- package/src/claude/skills/code-review/SKILL.md +25 -108
- package/src/claude/skills/code-review/references/spec-compliance-review.md +4 -7
- package/src/claude/skills/develop/SKILL.md +22 -70
- package/src/claude/skills/develop/references/subagent-patterns.md +0 -6
- package/src/claude/skills/research/SKILL.md +24 -55
- package/src/claude/skills/sync/SKILL.md +12 -34
- package/src/claude/skills/sync/references/rebind-and-audit.md +2 -2
- package/src/claude/skills/test/SKILL.md +39 -123
- package/src/claude/skills/test/references/execution-strategy.md +11 -22
- package/src/claude/skills/ui-ux/SKILL.md +1 -1
- package/src/claude/skills/ui-ux-pro-max/SKILL.md +24 -79
- package/src/claude/skills/web-testing/SKILL.md +2 -20
- package/src/codex/hooks/lib/spec-receipt.cjs +2 -30
- package/src/codex/hooks/lib/spec-utils.cjs +6 -45
- package/src/codex/hooks/spec-gate.cjs +11 -68
- package/src/codex/hooks/spec-state.cjs +37 -41
- package/src/common/AGENTS.md +4 -10
- package/bin/__tests__/change-firewall.semantic-firewall.test.js +0 -2133
- package/bin/__tests__/finalizer-pass-gate.semantic-firewall.test.js +0 -342
- package/bin/__tests__/release-preflight.semantic-firewall.test.js +0 -586
- package/bin/__tests__/review-receipt-c2.semantic-firewall.test.js +0 -378
- package/bin/__tests__/specs-v2-policy-and-scaffold.test.js +0 -665
- package/bin/__tests__/specs-v2-validator-grounder-contract.test.js +0 -1132
- package/src/claude/hooks/lib/context.cjs +0 -527
- package/src/claude/hooks/lib/detect.cjs +0 -474
- package/src/claude/scripts/change-firewall.cjs +0 -1748
- package/src/claude/scripts/spec-authoring-digest.cjs +0 -151
- package/src/claude/scripts/spec-authoring-validation.cjs +0 -126
- package/src/claude/scripts/spec-final-state.cjs +0 -290
- package/src/claude/scripts/spec-ground.cjs +0 -650
- package/src/claude/scripts/spec-readiness.cjs +0 -425
- package/src/claude/scripts/spec-scaffold.cjs +0 -2317
- package/src/claude/scripts/spec-semantic-model.cjs +0 -396
- package/src/claude/scripts/validate-spec-output.cjs +0 -3914
- package/src/claude/skills/code-review/references/verification-gate.md +0 -41
- package/src/claude/skills/specs/templates/design.md +0 -82
- package/src/claude/skills/specs/templates/requirements-init.md +0 -8
- package/src/claude/skills/specs/templates/requirements.md +0 -42
- package/src/claude/skills/specs/templates/spec-state.json +0 -56
- package/src/claude/skills/specs/templates/task.md +0 -47
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: cf:ui-ux-pro-max
|
|
3
|
-
description: "UI/UX
|
|
3
|
+
description: "Searchable UI/UX lookup database for web and mobile products: 50+ visual styles, 161 color palettes, 57 font pairings, 161 product types with reasoning rules, 99 UX guidelines, 25 chart types, Google Fonts, and React Native stack guidance. Use to pick or justify a palette, font pairing, visual style, chart type, or UX guideline for a product type. It does not build, review, or redesign screens; that is cf:ui-ux, which can consult this skill for lookups."
|
|
4
4
|
user-invocable: true
|
|
5
5
|
when_to_use: "Invoke to look up color palettes, font pairings, product-type styles, or UX guidelines; to build or review a screen use cf:ui-ux."
|
|
6
6
|
category: frontend
|
|
@@ -11,45 +11,13 @@ metadata:
|
|
|
11
11
|
---
|
|
12
12
|
# UI/UX Pro Max - Design Intelligence
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
Searchable design database for web and mobile products: 50+ styles, 161 color palettes, 57 font pairings, 161 product types with reasoning rules, 99 UX guidelines, 25 chart types, and stack guidance for React Native only.
|
|
15
15
|
|
|
16
16
|
## When to Apply
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
Use this skill to look something up: a palette, font pairing, or visual style for a product type, a chart type, or the UX guideline that applies to a pattern.
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
This Skill must be invoked in the following situations:
|
|
23
|
-
|
|
24
|
-
- Designing new pages (Landing Page, Dashboard, Admin, SaaS, Mobile App)
|
|
25
|
-
- Creating or refactoring UI components (buttons, modals, forms, tables, charts, etc.)
|
|
26
|
-
- Choosing color schemes, typography systems, spacing standards, or layout systems
|
|
27
|
-
- Reviewing UI code for user experience, accessibility, or visual consistency
|
|
28
|
-
- Implementing navigation structures, animations, or responsive behavior
|
|
29
|
-
- Making product-level design decisions (style, information hierarchy, brand expression)
|
|
30
|
-
- Improving perceived quality, clarity, or usability of interfaces
|
|
31
|
-
|
|
32
|
-
### Recommended
|
|
33
|
-
|
|
34
|
-
This Skill is recommended in the following situations:
|
|
35
|
-
|
|
36
|
-
- UI looks "not professional enough" but the reason is unclear
|
|
37
|
-
- Receiving feedback on usability or experience
|
|
38
|
-
- Pre-launch UI quality optimization
|
|
39
|
-
- Aligning cross-platform design (Web / iOS / Android)
|
|
40
|
-
- Building design systems or reusable component libraries
|
|
41
|
-
|
|
42
|
-
### Skip
|
|
43
|
-
|
|
44
|
-
This Skill is not needed in the following situations:
|
|
45
|
-
|
|
46
|
-
- Pure backend logic development
|
|
47
|
-
- Only involving API or database design
|
|
48
|
-
- Performance optimization unrelated to the interface
|
|
49
|
-
- Infrastructure or DevOps work
|
|
50
|
-
- Non-visual scripts or automation tasks
|
|
51
|
-
|
|
52
|
-
**Decision criteria**: If the task will change how a feature **looks, feels, moves, or is interacted with**, this Skill should be used.
|
|
20
|
+
Building, reviewing, or redesigning a screen belongs to `cf:ui-ux`, which follows the project's own components and measures the page; it can call this skill when it needs a lookup. Backend, API, infrastructure, and non-visual work need neither skill.
|
|
53
21
|
|
|
54
22
|
## Rule Categories by Priority
|
|
55
23
|
|
|
@@ -313,28 +281,7 @@ Search specific domains using the CLI tool below.
|
|
|
313
281
|
|
|
314
282
|
## Prerequisites
|
|
315
283
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
```bash
|
|
319
|
-
python3 --version || python --version
|
|
320
|
-
```
|
|
321
|
-
|
|
322
|
-
If Python is not installed, install it based on user's OS:
|
|
323
|
-
|
|
324
|
-
**macOS:**
|
|
325
|
-
```bash
|
|
326
|
-
brew install python3
|
|
327
|
-
```
|
|
328
|
-
|
|
329
|
-
**Ubuntu/Debian:**
|
|
330
|
-
```bash
|
|
331
|
-
sudo apt update && sudo apt install python3
|
|
332
|
-
```
|
|
333
|
-
|
|
334
|
-
**Windows:**
|
|
335
|
-
```powershell
|
|
336
|
-
winget install Python.Python.3.12
|
|
337
|
-
```
|
|
284
|
+
Run `search.py` from the project root with `python3 .claude/skills/ui-ux-pro-max/scripts/search.py` (Windows: `python .claude\skills\ui-ux-pro-max\scripts\search.py`). It uses only the Python standard library, so no venv or extra install is needed.
|
|
338
285
|
|
|
339
286
|
---
|
|
340
287
|
|
|
@@ -362,14 +309,14 @@ Extract key information from user request:
|
|
|
362
309
|
- **Product type**: Entertainment (social, video, music, gaming), Tool (scanner, editor, converter), Productivity (task manager, notes, calendar), or hybrid
|
|
363
310
|
- **Target audience**: C-end consumer users; consider age group, usage context (commute, leisure, work)
|
|
364
311
|
- **Style keywords**: playful, vibrant, minimal, dark mode, content-first, immersive, etc.
|
|
365
|
-
- **Stack**:
|
|
312
|
+
- **Stack**: the target project's stack; `--stack` guidance exists only for `react-native`
|
|
366
313
|
|
|
367
|
-
### Step 2: Generate Design System
|
|
314
|
+
### Step 2: Generate Design System
|
|
368
315
|
|
|
369
|
-
|
|
316
|
+
Start with `--design-system` when choosing a whole direction (style, palette, type) for a new product or page; for a single lookup go straight to Step 3:
|
|
370
317
|
|
|
371
318
|
```bash
|
|
372
|
-
python3 skills/ui-ux-pro-max/scripts/search.py "<product_type> <industry> <keywords>" --design-system [-p "Project Name"]
|
|
319
|
+
python3 .claude/skills/ui-ux-pro-max/scripts/search.py "<product_type> <industry> <keywords>" --design-system [-p "Project Name"]
|
|
373
320
|
```
|
|
374
321
|
|
|
375
322
|
This command:
|
|
@@ -380,7 +327,7 @@ This command:
|
|
|
380
327
|
|
|
381
328
|
**Example:**
|
|
382
329
|
```bash
|
|
383
|
-
python3 skills/ui-ux-pro-max/scripts/search.py "beauty spa wellness service" --design-system -p "Serenity Spa"
|
|
330
|
+
python3 .claude/skills/ui-ux-pro-max/scripts/search.py "beauty spa wellness service" --design-system -p "Serenity Spa"
|
|
384
331
|
```
|
|
385
332
|
|
|
386
333
|
### Step 2b: Persist Design System (Master + Overrides Pattern)
|
|
@@ -388,7 +335,7 @@ python3 skills/ui-ux-pro-max/scripts/search.py "beauty spa wellness service" --d
|
|
|
388
335
|
To save the design system for **hierarchical retrieval across sessions**, add `--persist`:
|
|
389
336
|
|
|
390
337
|
```bash
|
|
391
|
-
python3 skills/ui-ux-pro-max/scripts/search.py "<query>" --design-system --persist -p "Project Name"
|
|
338
|
+
python3 .claude/skills/ui-ux-pro-max/scripts/search.py "<query>" --design-system --persist -p "Project Name"
|
|
392
339
|
```
|
|
393
340
|
|
|
394
341
|
This creates:
|
|
@@ -397,7 +344,7 @@ This creates:
|
|
|
397
344
|
|
|
398
345
|
**With page-specific override:**
|
|
399
346
|
```bash
|
|
400
|
-
python3 skills/ui-ux-pro-max/scripts/search.py "<query>" --design-system --persist -p "Project Name" --page "dashboard"
|
|
347
|
+
python3 .claude/skills/ui-ux-pro-max/scripts/search.py "<query>" --design-system --persist -p "Project Name" --page "dashboard"
|
|
401
348
|
```
|
|
402
349
|
|
|
403
350
|
This also creates:
|
|
@@ -422,7 +369,7 @@ Now, generate the code...
|
|
|
422
369
|
After getting the design system, use domain searches to get additional details:
|
|
423
370
|
|
|
424
371
|
```bash
|
|
425
|
-
python3 skills/ui-ux-pro-max/scripts/search.py "<keyword>" --domain <domain> [-n <max_results>]
|
|
372
|
+
python3 .claude/skills/ui-ux-pro-max/scripts/search.py "<keyword>" --domain <domain> [-n <max_results>]
|
|
426
373
|
```
|
|
427
374
|
|
|
428
375
|
**When to use detailed searches:**
|
|
@@ -440,14 +387,13 @@ python3 skills/ui-ux-pro-max/scripts/search.py "<keyword>" --domain <domain> [-n
|
|
|
440
387
|
| Landing structure | `landing` | `--domain landing "hero social-proof"` |
|
|
441
388
|
| React Native perf | `react` | `--domain react "rerender memo list"` |
|
|
442
389
|
| App interface a11y | `web` | `--domain web "accessibilityLabel touch safe-areas"` |
|
|
443
|
-
| AI prompt / CSS keywords | `prompt` | `--domain prompt "minimalism"` |
|
|
444
390
|
|
|
445
|
-
### Step 4: Stack Guidelines (React Native)
|
|
391
|
+
### Step 4: Stack Guidelines (React Native projects only)
|
|
446
392
|
|
|
447
|
-
|
|
393
|
+
For a React Native project, get implementation-specific best practices:
|
|
448
394
|
|
|
449
395
|
```bash
|
|
450
|
-
python3 skills/ui-ux-pro-max/scripts/search.py "<keyword>" --stack react-native
|
|
396
|
+
python3 .claude/skills/ui-ux-pro-max/scripts/search.py "<keyword>" --stack react-native
|
|
451
397
|
```
|
|
452
398
|
|
|
453
399
|
---
|
|
@@ -468,7 +414,6 @@ python3 skills/ui-ux-pro-max/scripts/search.py "<keyword>" --stack react-native
|
|
|
468
414
|
| `google-fonts` | Individual Google Fonts lookup | sans serif, monospace, japanese, variable font, popular |
|
|
469
415
|
| `react` | React/Next.js performance | waterfall, bundle, suspense, memo, rerender, cache |
|
|
470
416
|
| `web` | App interface guidelines (iOS/Android/React Native) | accessibilityLabel, touch targets, safe areas, Dynamic Type |
|
|
471
|
-
| `prompt` | AI prompts, CSS keywords | (style name) |
|
|
472
417
|
|
|
473
418
|
### Available Stacks
|
|
474
419
|
|
|
@@ -488,10 +433,10 @@ python3 skills/ui-ux-pro-max/scripts/search.py "<keyword>" --stack react-native
|
|
|
488
433
|
- Style keywords: modern, minimal, content-first, dark mode
|
|
489
434
|
- Stack: React Native
|
|
490
435
|
|
|
491
|
-
### Step 2: Generate Design System
|
|
436
|
+
### Step 2: Generate Design System
|
|
492
437
|
|
|
493
438
|
```bash
|
|
494
|
-
python3 skills/ui-ux-pro-max/scripts/search.py "AI search tool modern minimal" --design-system -p "AI Search"
|
|
439
|
+
python3 .claude/skills/ui-ux-pro-max/scripts/search.py "AI search tool modern minimal" --design-system -p "AI Search"
|
|
495
440
|
```
|
|
496
441
|
|
|
497
442
|
**Output:** Complete design system with pattern, style, colors, typography, effects, and anti-patterns.
|
|
@@ -500,16 +445,16 @@ python3 skills/ui-ux-pro-max/scripts/search.py "AI search tool modern minimal" -
|
|
|
500
445
|
|
|
501
446
|
```bash
|
|
502
447
|
# Get style options for a modern tool product
|
|
503
|
-
python3 skills/ui-ux-pro-max/scripts/search.py "minimalism dark mode" --domain style
|
|
448
|
+
python3 .claude/skills/ui-ux-pro-max/scripts/search.py "minimalism dark mode" --domain style
|
|
504
449
|
|
|
505
450
|
# Get UX best practices for search interaction and loading
|
|
506
|
-
python3 skills/ui-ux-pro-max/scripts/search.py "search loading animation" --domain ux
|
|
451
|
+
python3 .claude/skills/ui-ux-pro-max/scripts/search.py "search loading animation" --domain ux
|
|
507
452
|
```
|
|
508
453
|
|
|
509
454
|
### Step 4: Stack Guidelines
|
|
510
455
|
|
|
511
456
|
```bash
|
|
512
|
-
python3 skills/ui-ux-pro-max/scripts/search.py "list performance navigation" --stack react-native
|
|
457
|
+
python3 .claude/skills/ui-ux-pro-max/scripts/search.py "list performance navigation" --stack react-native
|
|
513
458
|
```
|
|
514
459
|
|
|
515
460
|
**Then:** Synthesize design system + detailed searches and implement the design.
|
|
@@ -522,10 +467,10 @@ The `--design-system` flag supports two output formats:
|
|
|
522
467
|
|
|
523
468
|
```bash
|
|
524
469
|
# ASCII box (default) - best for terminal display
|
|
525
|
-
python3 skills/ui-ux-pro-max/scripts/search.py "fintech crypto" --design-system
|
|
470
|
+
python3 .claude/skills/ui-ux-pro-max/scripts/search.py "fintech crypto" --design-system
|
|
526
471
|
|
|
527
472
|
# Markdown - best for documentation
|
|
528
|
-
python3 skills/ui-ux-pro-max/scripts/search.py "fintech crypto" --design-system -f markdown
|
|
473
|
+
python3 .claude/skills/ui-ux-pro-max/scripts/search.py "fintech crypto" --design-system -f markdown
|
|
529
474
|
```
|
|
530
475
|
|
|
531
476
|
---
|
|
@@ -537,7 +482,7 @@ python3 skills/ui-ux-pro-max/scripts/search.py "fintech crypto" --design-system
|
|
|
537
482
|
- Use **multi-dimensional keywords** — combine product + industry + tone + density: `"entertainment social vibrant content-dense"` not just `"app"`
|
|
538
483
|
- Try different keywords for the same need: `"playful neon"` → `"vibrant dark"` → `"content-first minimal"`
|
|
539
484
|
- Use `--design-system` first for full recommendations, then `--domain` to deep-dive any dimension you're unsure about
|
|
540
|
-
-
|
|
485
|
+
- Add `--stack react-native` only when the target project is React Native
|
|
541
486
|
|
|
542
487
|
### Common Sticking Points
|
|
543
488
|
|
|
@@ -26,15 +26,9 @@ npx @axe-core/cli https://example.com # Accessibility
|
|
|
26
26
|
npx lighthouse https://example.com # Performance
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
## Testing Strategy
|
|
29
|
+
## Testing Strategy
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|-------|-----------|----------|
|
|
33
|
-
| Pyramid | Unit 70% > Integration 20% > E2E 10% | Monoliths |
|
|
34
|
-
| Trophy | Integration-heavy | Modern SPAs |
|
|
35
|
-
| Honeycomb | Contract-centric | Microservices |
|
|
36
|
-
|
|
37
|
-
→ `./references/testing-pyramid-strategy.md`
|
|
31
|
+
Choosing between pyramid, trophy, and honeycomb: `./references/testing-pyramid-strategy.md`
|
|
38
32
|
|
|
39
33
|
## Reference Documentation
|
|
40
34
|
|
|
@@ -88,15 +82,3 @@ node ./scripts/analyze-test-results.js \
|
|
|
88
82
|
--output markdown
|
|
89
83
|
```
|
|
90
84
|
Parses Playwright/Vitest/JUnit results into unified summary.
|
|
91
|
-
|
|
92
|
-
## CI/CD Integration
|
|
93
|
-
|
|
94
|
-
```yaml
|
|
95
|
-
jobs:
|
|
96
|
-
test:
|
|
97
|
-
steps:
|
|
98
|
-
- run: npm run test:unit # Gate 1: Fast fail
|
|
99
|
-
- run: npm run test:e2e # Gate 2: After unit pass
|
|
100
|
-
- run: npm run test:a11y # Accessibility
|
|
101
|
-
- run: npx lhci autorun # Performance
|
|
102
|
-
```
|
|
@@ -37,8 +37,8 @@ function loadSharedReceipt() {
|
|
|
37
37
|
'spec-receipt.cjs',
|
|
38
38
|
'spec-receipt.cjs',
|
|
39
39
|
(receipt) => {
|
|
40
|
-
if (typeof receipt?.
|
|
41
|
-
throw new Error('shared spec receipt helper has no
|
|
40
|
+
if (typeof receipt?.checkWorkflowTaskReceipt !== 'function') {
|
|
41
|
+
throw new Error('shared spec receipt helper has no checkWorkflowTaskReceipt function');
|
|
42
42
|
}
|
|
43
43
|
},
|
|
44
44
|
'shared spec receipt helper is missing'
|
|
@@ -55,7 +55,6 @@ function receiptHelper() {
|
|
|
55
55
|
return loaded.receipt;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
function evidenceBody(text) { return receiptHelper().evidenceBody(text, getSharedPolicy() || {}); }
|
|
59
58
|
function safeTaskFile(featureDir, taskPath) {
|
|
60
59
|
const result = receiptHelper().safeRead(featureDir, taskPath);
|
|
61
60
|
return result.status === 'ok' ? result.path : null;
|
|
@@ -64,11 +63,6 @@ function validateCanonicalReceipt(body, options = {}) {
|
|
|
64
63
|
const validate = getSharedValidate();
|
|
65
64
|
return validate ? validate(body, options) : ['shared_validator'];
|
|
66
65
|
}
|
|
67
|
-
function checkReceiptDetails(featureDir, taskPath, task, runtimeContext) {
|
|
68
|
-
const policy = getSharedPolicy();
|
|
69
|
-
if (!policy) return { failures: ['shared_validator'], status: 'missing' };
|
|
70
|
-
return receiptHelper().checkTaskReceipt(featureDir, taskPath, task, runtimeContext, policy);
|
|
71
|
-
}
|
|
72
66
|
function checkWorkflowReceiptDetails(featureDir, taskPath, runtimeContext) {
|
|
73
67
|
const policy = getSharedPolicy();
|
|
74
68
|
const receipt = receiptHelper();
|
|
@@ -85,40 +79,18 @@ function checkWorkflowReceiptSet(candidates, projectRoot, runtimeSession) {
|
|
|
85
79
|
}
|
|
86
80
|
return receipt.checkWorkflowReceiptSet(candidates, projectRoot, runtimeSession, policy);
|
|
87
81
|
}
|
|
88
|
-
function checkReceipt(featureDir, taskPath, task, runtimeContext) {
|
|
89
|
-
const map = {
|
|
90
|
-
unsafe_path: 'a', missing_receipt: 'b', task_status: 'a', completed_at: 'd',
|
|
91
|
-
verification_state: 'c', placeholder: 'c', validator_unavailable: 'c',
|
|
92
|
-
command: 'e', command_identity: 'e', exit_result: 'f', provenance: 'g',
|
|
93
|
-
artifact_hash: 'h', artifact_declaration: 'h',
|
|
94
|
-
};
|
|
95
|
-
return [...new Set(checkReceiptDetails(featureDir, taskPath, task, runtimeContext).failures.map((failure) => map[failure] || failure))];
|
|
96
|
-
}
|
|
97
|
-
function checkFeatureReceipt(featureDir, runtimeContext) {
|
|
98
|
-
const policy = getSharedPolicy();
|
|
99
|
-
if (!policy) return { failures: ['shared_validator'], status: 'missing' };
|
|
100
|
-
return receiptHelper().checkFeatureReceipt(featureDir, runtimeContext, policy);
|
|
101
|
-
}
|
|
102
82
|
function receiptFixHint(failures, body) {
|
|
103
83
|
const helper = receiptHelper();
|
|
104
84
|
return typeof helper.receiptFixHint === 'function' ? helper.receiptFixHint(failures, body) : null;
|
|
105
85
|
}
|
|
106
|
-
function readTaskProof(featureDir, taskPath) {
|
|
107
|
-
return receiptHelper().readTaskProof(featureDir, taskPath, getSharedPolicy() || {});
|
|
108
|
-
}
|
|
109
86
|
|
|
110
87
|
module.exports = {
|
|
111
|
-
checkFeatureReceipt,
|
|
112
|
-
checkReceipt,
|
|
113
|
-
checkReceiptDetails,
|
|
114
88
|
checkWorkflowReceiptDetails,
|
|
115
89
|
checkWorkflowReceiptSet,
|
|
116
|
-
evidenceBody,
|
|
117
90
|
getSharedPolicy,
|
|
118
91
|
getSharedValidate,
|
|
119
92
|
loadSharedPolicy,
|
|
120
93
|
loadSharedReceipt,
|
|
121
|
-
readTaskProof,
|
|
122
94
|
receiptFixHint,
|
|
123
95
|
safeTaskFile,
|
|
124
96
|
validateCanonicalReceipt,
|
|
@@ -66,8 +66,8 @@ function sharedResolver() {
|
|
|
66
66
|
// Use canonical realpath for require to avoid symlink hijack
|
|
67
67
|
const realCandidate = canonicalRealpath(candidate);
|
|
68
68
|
const resolver = require(realCandidate);
|
|
69
|
-
if (typeof resolver?.
|
|
70
|
-
throw new Error('shared spec resolver has no
|
|
69
|
+
if (typeof resolver?.resolveWorkflowCandidate !== 'function') {
|
|
70
|
+
throw new Error('shared spec resolver has no resolveWorkflowCandidate function');
|
|
71
71
|
}
|
|
72
72
|
return resolver;
|
|
73
73
|
} catch (error) {
|
|
@@ -80,31 +80,13 @@ function specsDirectory(projectRoot, runtime) {
|
|
|
80
80
|
return sharedResolver().specsDirectory(projectRoot, runtime);
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
function
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
function findAllSpecCandidates(projectRoot, runtime) {
|
|
88
|
-
return sharedResolver().findAllSpecCandidates(projectRoot, runtime);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function resolveActiveSpec(projectRoot, runtime, explicitFeature, explicitPath) {
|
|
92
|
-
if (projectRoot && typeof projectRoot === 'object') {
|
|
93
|
-
return sharedResolver().resolveActiveSpec(projectRoot);
|
|
94
|
-
}
|
|
95
|
-
return sharedResolver().resolveActiveSpec({
|
|
96
|
-
projectRoot,
|
|
97
|
-
runtime,
|
|
98
|
-
explicitFeature,
|
|
99
|
-
explicitPath,
|
|
100
|
-
});
|
|
83
|
+
function findLegacyPackets(projectRoot, runtime) {
|
|
84
|
+
const resolver = sharedResolver();
|
|
85
|
+
return typeof resolver.findLegacyPackets === 'function' ? resolver.findLegacyPackets(projectRoot, runtime) : [];
|
|
101
86
|
}
|
|
102
87
|
|
|
103
88
|
function resolveWorkflowCandidate(projectRoot, runtime, explicitFeature, explicitPath) {
|
|
104
89
|
const resolver = sharedResolver();
|
|
105
|
-
if (typeof resolver.resolveWorkflowCandidate !== 'function') {
|
|
106
|
-
return resolveActiveSpec(projectRoot, runtime, explicitFeature, explicitPath);
|
|
107
|
-
}
|
|
108
90
|
if (projectRoot && typeof projectRoot === 'object') {
|
|
109
91
|
return resolver.resolveWorkflowCandidate(projectRoot);
|
|
110
92
|
}
|
|
@@ -123,23 +105,6 @@ function refineWorkflowGateResolution(resolved) {
|
|
|
123
105
|
: resolved;
|
|
124
106
|
}
|
|
125
107
|
|
|
126
|
-
function findActiveSpec(projectRoot, runtime) {
|
|
127
|
-
const resolved = resolveActiveSpec(projectRoot, runtime);
|
|
128
|
-
if (!resolved) return null;
|
|
129
|
-
if (resolved.error === 'multiple_active' || resolved.error === 'invalid_specs') return resolved;
|
|
130
|
-
if (resolved.error) return null;
|
|
131
|
-
return resolved;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function taskStatusMap(spec) {
|
|
135
|
-
return Object.fromEntries(
|
|
136
|
-
Object.entries(spec.task_registry || {}).map(([taskPath, task]) => [
|
|
137
|
-
taskPath,
|
|
138
|
-
task?.status || 'pending',
|
|
139
|
-
]),
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
108
|
function extractExplicitTarget(...sources) {
|
|
144
109
|
const resolver = sharedResolver();
|
|
145
110
|
if (typeof resolver.extractExplicitTarget !== 'function') return null;
|
|
@@ -153,14 +118,10 @@ function readActiveFeatureTarget(options) {
|
|
|
153
118
|
}
|
|
154
119
|
|
|
155
120
|
module.exports = {
|
|
156
|
-
findActiveSpec,
|
|
157
121
|
extractExplicitTarget,
|
|
122
|
+
findLegacyPackets,
|
|
158
123
|
readActiveFeatureTarget,
|
|
159
|
-
findAllActiveSpecs,
|
|
160
|
-
findAllSpecCandidates,
|
|
161
|
-
resolveActiveSpec,
|
|
162
124
|
resolveWorkflowCandidate,
|
|
163
125
|
refineWorkflowGateResolution,
|
|
164
126
|
specsDirectory,
|
|
165
|
-
taskStatusMap,
|
|
166
127
|
};
|
|
@@ -11,14 +11,11 @@ const {
|
|
|
11
11
|
readPayload
|
|
12
12
|
} = require('./lib/hook-context.cjs');
|
|
13
13
|
const {
|
|
14
|
-
checkFeatureReceipt,
|
|
15
|
-
checkReceiptDetails,
|
|
16
14
|
checkWorkflowReceiptDetails,
|
|
17
15
|
checkWorkflowReceiptSet,
|
|
18
16
|
loadSharedPolicy,
|
|
19
17
|
receiptFixHint,
|
|
20
18
|
} = require('./lib/spec-receipt.cjs');
|
|
21
|
-
const { taskStatusMap } = require('./lib/spec-utils.cjs');
|
|
22
19
|
|
|
23
20
|
function emitBlock(reason) {
|
|
24
21
|
process.stdout.write(`${JSON.stringify({ decision: 'block', reason })}\n`);
|
|
@@ -45,7 +42,7 @@ try {
|
|
|
45
42
|
}
|
|
46
43
|
const POLICY = loaded.policy;
|
|
47
44
|
const { projectRoot, runtime } = getHookContext(payload);
|
|
48
|
-
if (typeof POLICY.
|
|
45
|
+
if (typeof POLICY.deriveRuntimeContext !== 'function') {
|
|
49
46
|
throw new Error('shared workflow policy lacks completion authority functions');
|
|
50
47
|
}
|
|
51
48
|
const resolver = require('./lib/spec-utils.cjs');
|
|
@@ -88,7 +85,7 @@ try {
|
|
|
88
85
|
if (resolved.error === 'invalid_specs') {
|
|
89
86
|
process.stdout.write(`${JSON.stringify({
|
|
90
87
|
decision: 'block',
|
|
91
|
-
reason: `Completion gate: invalid
|
|
88
|
+
reason: `Completion gate: invalid workflow packet detected (${resolved.candidates.join(', ')}): ${resolved.reason}. Fix the packet's plan.md or task files before completing tasks.`
|
|
92
89
|
})}\n`);
|
|
93
90
|
process.exit(0);
|
|
94
91
|
}
|
|
@@ -101,20 +98,10 @@ try {
|
|
|
101
98
|
}
|
|
102
99
|
if (resolved.error) process.exit(0);
|
|
103
100
|
const active = resolved;
|
|
104
|
-
const activeSpec = active.spec || {};
|
|
105
|
-
const processWorkflow = active.layoutKind === 'process-v3';
|
|
106
|
-
const lifecyclePhase = activeSpec.current_phase || activeSpec.phase;
|
|
107
|
-
const explicitCloseout = ['done', 'completed', 'complete'].includes(activeSpec.status)
|
|
108
|
-
|| ['closeout', 'completion', 'completed', 'complete'].includes(lifecyclePhase);
|
|
109
|
-
// Legacy 2.1 closeout is retired; a 2.1 packet mid-execution keeps the ordinary receipt checks.
|
|
110
|
-
if (!processWorkflow && activeSpec.schema_version === '2.1' && explicitCloseout) {
|
|
111
|
-
emitBlock('Completion gate: Legacy spec.json closeout is no longer supported; move the packet to plan.md with flat task files');
|
|
112
|
-
process.exit(0);
|
|
113
|
-
}
|
|
114
101
|
const runtimeContext = POLICY.deriveRuntimeContext({
|
|
115
102
|
projectRoot,
|
|
116
103
|
specsRoot: active.specsDir,
|
|
117
|
-
specFile: active.stateFile
|
|
104
|
+
specFile: active.stateFile,
|
|
118
105
|
featureName: active.featureName,
|
|
119
106
|
runtimeSession: sessionIdentity(payload),
|
|
120
107
|
});
|
|
@@ -123,10 +110,10 @@ try {
|
|
|
123
110
|
emitBlock('Completion gate: runtime.spec.completion_gate is a worker-writable flag, not an authorization; no completion-gate bypass is supported. Remove the flag and satisfy the gate.');
|
|
124
111
|
process.exit(0);
|
|
125
112
|
}
|
|
126
|
-
const registry = active.taskRegistry ||
|
|
127
|
-
const currentStatuses =
|
|
128
|
-
|
|
129
|
-
|
|
113
|
+
const registry = active.taskRegistry || {};
|
|
114
|
+
const currentStatuses = Object.fromEntries(
|
|
115
|
+
Object.entries(registry).map(([taskPath, task]) => [taskPath, task?.status || 'pending']),
|
|
116
|
+
);
|
|
130
117
|
const cacheFile = path.join(hookStateDir(projectRoot), 'spec-gate-last.json');
|
|
131
118
|
const cacheExists = fs.existsSync(cacheFile);
|
|
132
119
|
let cache = {};
|
|
@@ -152,16 +139,6 @@ try {
|
|
|
152
139
|
const previous = cacheExists && cacheEntries[runtimeContext.context_id]
|
|
153
140
|
? cacheEntries[runtimeContext.context_id].tasks || {}
|
|
154
141
|
: {};
|
|
155
|
-
const staleFlashTasks = processWorkflow ? [] : Object.entries(registry)
|
|
156
|
-
.filter(([, task]) => POLICY.isStaleFlashDone(task))
|
|
157
|
-
.map(([taskPath]) => taskPath);
|
|
158
|
-
if (staleFlashTasks.length > 0) {
|
|
159
|
-
process.stdout.write(`${JSON.stringify({
|
|
160
|
-
decision: 'block',
|
|
161
|
-
reason: `Completion gate: ${staleFlashTasks.length} task(s) marked done with FLASH_UNVERIFIED (${staleFlashTasks.join(', ')}). Run /cf:test, then use explicit sync-finalize.`
|
|
162
|
-
})}\n`);
|
|
163
|
-
process.exit(0);
|
|
164
|
-
}
|
|
165
142
|
const allDoneTasks = Object.keys(registry).filter((taskPath) => (
|
|
166
143
|
currentStatuses[taskPath] === 'done'
|
|
167
144
|
));
|
|
@@ -169,9 +146,7 @@ try {
|
|
|
169
146
|
const receiptBodies = new Map();
|
|
170
147
|
const failures = allDoneTasks
|
|
171
148
|
.map((taskPath) => {
|
|
172
|
-
const proof =
|
|
173
|
-
? checkWorkflowReceiptDetails(featureDir, taskPath, runtimeContext)
|
|
174
|
-
: checkReceiptDetails(featureDir, taskPath, registry[taskPath], runtimeContext);
|
|
149
|
+
const proof = checkWorkflowReceiptDetails(featureDir, taskPath, runtimeContext);
|
|
175
150
|
if (proof.body) receiptBodies.set(taskPath, proof.body);
|
|
176
151
|
return {
|
|
177
152
|
taskPath,
|
|
@@ -180,52 +155,20 @@ try {
|
|
|
180
155
|
})
|
|
181
156
|
.filter((result) => result.failures.length);
|
|
182
157
|
|
|
183
|
-
const featureCloseoutRequired = !processWorkflow && explicitCloseout && allDoneTasks.length > 0;
|
|
184
|
-
const featureReceipt = featureCloseoutRequired
|
|
185
|
-
? checkFeatureReceipt(featureDir, runtimeContext)
|
|
186
|
-
: null;
|
|
187
|
-
if (featureReceipt && featureReceipt.failures.length) {
|
|
188
|
-
failures.push({ taskPath: 'feature-receipt.md', failures: featureReceipt.failures });
|
|
189
|
-
}
|
|
190
|
-
const completion = featureCloseoutRequired && featureReceipt?.failures.length === 0
|
|
191
|
-
&& Object.prototype.hasOwnProperty.call(active.spec, 'workflow_policy')
|
|
192
|
-
? POLICY.completionDecisionForSpec(active.spec, {
|
|
193
|
-
runtimeContext,
|
|
194
|
-
executionReceipt: featureReceipt.body,
|
|
195
|
-
taskContext: {},
|
|
196
|
-
})
|
|
197
|
-
: null;
|
|
198
|
-
|
|
199
158
|
const next = { ...previous, ...currentStatuses };
|
|
200
159
|
for (const result of failures) {
|
|
201
160
|
if (previous[result.taskPath] === undefined) delete next[result.taskPath];
|
|
202
161
|
else next[result.taskPath] = previous[result.taskPath];
|
|
203
162
|
}
|
|
204
163
|
atomicWrite(cacheFile, `${JSON.stringify({
|
|
205
|
-
schema_version: '2',
|
|
206
164
|
entries: { ...cacheEntries, [runtimeContext.context_id]: { identity: cacheIdentity, tasks: next } },
|
|
207
165
|
})}\n`);
|
|
208
166
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
const lines = [
|
|
212
|
-
failures.length > 0
|
|
213
|
-
? `Completion gate: ${failures.length} done task(s) lack a verification receipt.`
|
|
214
|
-
: 'Completion gate: workflow completion proof is incomplete.'
|
|
215
|
-
];
|
|
167
|
+
if (!failures.length) process.exit(0);
|
|
168
|
+
const lines = [`Completion gate: ${failures.length} done task(s) lack a verification receipt.`];
|
|
216
169
|
for (const result of failures) {
|
|
217
170
|
lines.push(`- \`${result.taskPath}\`: failed check(s) ${result.failures.join(', ')}`);
|
|
218
|
-
lines.push(result.taskPath
|
|
219
|
-
? ` Run final integration proof, then write \`specs/${active.featureName}/feature-receipt.md\`.`
|
|
220
|
-
: processWorkflow
|
|
221
|
-
? ` In \`specs/${active.featureName}/${path.posix.basename(result.taskPath)}\`: ${receiptFixHint(result.failures, result.body) || 'write a runtime-bound `## Receipt` with command output'}.`
|
|
222
|
-
: ` Write canonical proof to \`specs/${active.featureName}/receipts/${path.posix.basename(result.taskPath)}\`; legacy \`## Evidence\` remains read-compatible.`);
|
|
223
|
-
}
|
|
224
|
-
if (completionBlocked) {
|
|
225
|
-
lines.push(`- Completion decision unfinished: ${completion.blocker || 'required workflow proof is missing.'}`);
|
|
226
|
-
if (Array.isArray(completion.missingProof) && completion.missingProof.length > 0) {
|
|
227
|
-
lines.push(` Missing proof: ${completion.missingProof.join(', ')}`);
|
|
228
|
-
}
|
|
171
|
+
lines.push(` In \`specs/${active.featureName}/${path.posix.basename(result.taskPath)}\`: ${receiptFixHint(result.failures, result.body) || 'write a runtime-bound `## Receipt` with command output'}.`);
|
|
229
172
|
}
|
|
230
173
|
process.stdout.write(`${JSON.stringify({
|
|
231
174
|
decision: 'block',
|