@haposoft/cafekit 0.16.0-rc.7 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +116 -27
- package/bin/__tests__/codex-hooks-ownership.test.js +239 -0
- package/bin/__tests__/codex-hooks.test.js +295 -0
- package/bin/__tests__/codex-native.test.js +2289 -34
- package/bin/__tests__/develop-contract.test.js +400 -23
- package/bin/__tests__/installer-safety.test.js +3 -3
- package/bin/__tests__/optional-skill-inventory.test.js +249 -0
- package/bin/__tests__/output-styles.test.js +214 -0
- package/bin/__tests__/package-inventory.test.js +2131 -16
- package/bin/__tests__/runtime-schema.test.js +111 -0
- package/bin/__tests__/secret-output-guardrail.test.js +122 -0
- package/bin/__tests__/skill-routing-source.test.js +172 -0
- package/bin/__tests__/specs-v2-execution-closeout.test.js +1 -1
- package/bin/install.js +10 -0
- package/bin/lib/codex-install.js +170 -5
- package/bin/lib/context.js +14 -0
- package/bin/lib/i18n.js +12 -6
- package/bin/lib/skill-deps.js +5 -5
- package/bin/phases/claude-runtime.js +23 -0
- package/bin/phases/claude-settings.js +8 -0
- package/bin/phases/codex-hooks.js +156 -0
- package/bin/phases/codex-runtime.js +29 -38
- package/bin/phases/copy-payload.js +8 -2
- package/bin/phases/select-skill-bundles.js +96 -0
- package/bin/phases/skill-inventory.js +71 -0
- package/bin/phases/skills-setup.js +9 -2
- package/bin/phases/write-metadata.js +8 -5
- package/package.json +8 -2
- package/src/claude/CLAUDE.md +3 -3
- package/src/claude/agents/brainstormer.md +52 -69
- package/src/claude/agents/code-auditor.md +32 -14
- package/src/claude/agents/debugger.md +35 -20
- package/src/claude/agents/deployer.md +12 -5
- package/src/claude/agents/docs-keeper.md +11 -1
- package/src/claude/agents/implementer.md +25 -13
- package/src/claude/agents/inspector.md +6 -3
- package/src/claude/agents/project-manager.md +1 -1
- package/src/claude/agents/researcher.md +26 -55
- package/src/claude/agents/spec-maker.md +24 -13
- package/src/claude/agents/strategist.md +52 -0
- package/src/claude/agents/test-runner.md +117 -132
- package/src/claude/agents/ui-ux-designer.md +1 -1
- package/src/claude/hooks/__tests__/fixtures/statusline-default.golden +1 -0
- package/src/claude/hooks/__tests__/precompact.test.js +158 -0
- package/src/claude/hooks/__tests__/spec-gate.test.js +87 -4
- package/src/claude/hooks/__tests__/state.test.js +254 -3
- package/src/claude/hooks/__tests__/statusline.test.js +196 -0
- package/src/claude/hooks/agent.cjs +1 -1
- package/src/claude/hooks/docs-sync.cjs +12 -10
- package/src/claude/hooks/inspect-block.cjs +1 -1
- package/src/claude/hooks/lib/config.cjs +3 -0
- package/src/claude/hooks/lib/hook-state-dir.cjs +39 -0
- package/src/claude/hooks/lib/secret-keywords.cjs +42 -0
- package/src/claude/hooks/precompact.cjs +105 -0
- package/src/claude/hooks/rules.cjs +1 -1
- package/src/claude/hooks/secret-output-guardrail.cjs +43 -0
- package/src/claude/hooks/session.cjs +27 -1
- package/src/claude/hooks/spec-gate.cjs +3 -3
- package/src/claude/hooks/spec-state.cjs +32 -5
- package/src/claude/hooks/state.cjs +1 -1
- package/src/claude/hooks/task-scaffold-guard.cjs +1 -1
- package/src/claude/hooks/usage.cjs +1 -1
- package/src/claude/migration-manifest.json +29 -15
- package/src/claude/output-styles/coding-level-0-eli5.md +103 -0
- package/src/claude/output-styles/coding-level-1-junior.md +124 -0
- package/src/claude/output-styles/coding-level-2-mid.md +146 -0
- package/src/claude/output-styles/coding-level-3-senior.md +148 -0
- package/src/claude/output-styles/coding-level-4-lead.md +159 -0
- package/src/claude/output-styles/coding-level-5-god.md +91 -0
- package/src/claude/references/debugger/core-philosophy.md +5 -3
- package/src/claude/references/debugger/frontend-verification.md +2 -2
- package/src/claude/references/debugger/log-ci-analysis.md +13 -1
- package/src/claude/references/debugger/parallel-agent-hydration.md +19 -34
- package/src/claude/references/debugger/performance-diagnostics.md +2 -2
- package/src/claude/references/debugger/root-cause-tracing.md +37 -6
- package/src/claude/references/debugger/side-effect-gate.md +3 -1
- package/src/claude/references/debugger/verification-protocol.md +13 -12
- package/src/claude/rules/orchestrator.md +7 -1
- package/src/claude/rules/process-management.md +39 -0
- package/src/claude/rules/review-audit-self-decision.md +48 -0
- package/src/claude/rules/skill-domain-routing.md +26 -24
- package/src/claude/rules/skill-workflow-routing.md +22 -14
- package/src/claude/rules/workflow.md +3 -3
- package/src/claude/runtime.json +1 -0
- package/src/claude/runtime.schema.json +205 -0
- package/src/claude/scripts/generate-skill-catalog.cjs +156 -122
- package/src/claude/scripts/provenance.cjs +18 -5
- package/src/claude/scripts/spec-receipt.cjs +76 -21
- package/src/claude/scripts/spec-resolver.cjs +144 -4
- package/src/claude/scripts/workflow-policy.cjs +1 -1
- package/src/claude/settings/settings.json +16 -0
- package/src/claude/skills/agent-browser/SKILL.md +1 -1
- package/src/claude/skills/ai-multimodal/SKILL.md +1 -1
- package/src/claude/skills/brainstorm/SKILL.md +193 -172
- package/src/claude/skills/brainstorm/references/question-framework.md +105 -167
- package/src/claude/skills/chrome-devtools/SKILL.md +1 -1
- package/src/claude/skills/code-review/SKILL.md +43 -25
- package/src/claude/skills/code-review/references/spec-compliance-review.md +3 -4
- package/src/claude/skills/code-review/references/verification-gate.md +1 -1
- package/src/claude/skills/debug/SKILL.md +48 -19
- package/src/claude/skills/delegate/SKILL.md +1 -1
- package/src/claude/skills/develop/SKILL.md +92 -65
- package/src/claude/skills/develop/references/parallel-waves.md +36 -63
- package/src/claude/skills/develop/references/quality-gate.md +31 -4
- package/src/claude/skills/develop/references/subagent-patterns.md +21 -7
- package/src/claude/skills/docs/SKILL.md +42 -23
- package/src/claude/skills/docs/references/init-workflow.md +4 -4
- package/src/claude/skills/docs/references/reconstruct-workflow.md +6 -6
- package/src/claude/skills/docs/references/standard-docs-workflow.md +6 -2
- package/src/claude/skills/docs/references/summarize-workflow.md +2 -2
- package/src/claude/skills/docs/references/update-workflow.md +9 -7
- package/src/claude/skills/docs/templates/requirements-as-is.md +1 -1
- package/src/claude/skills/docx/SKILL.md +1 -1
- package/src/claude/skills/git/SKILL.md +1 -1
- package/src/claude/skills/git/references/commit-protocols.md +1 -1
- package/src/claude/skills/git/references/finish-branch.md +1 -1
- package/src/claude/skills/hotfix/SKILL.md +159 -170
- package/src/claude/skills/hotfix/references/diagnosis-protocol.md +14 -9
- package/src/claude/skills/hotfix/references/parallel-patterns.md +27 -13
- package/src/claude/skills/hotfix/references/prevention-gate.md +5 -0
- package/src/claude/skills/hotfix/references/review-cycle.md +21 -15
- package/src/claude/skills/hotfix/references/workflow-specialized.md +53 -21
- package/src/claude/skills/inspect/SKILL.md +55 -46
- package/src/claude/skills/inspect/references/internal-inspection.md +21 -5
- package/src/claude/skills/loop/SKILL.md +114 -0
- package/src/claude/skills/loop/references/bounded-loop-protocol.md +104 -0
- package/src/claude/skills/loop/references/metric-and-guard-contract.md +76 -0
- package/src/claude/skills/pdf/SKILL.md +1 -1
- package/src/claude/skills/pptx/SKILL.md +1 -1
- package/src/claude/skills/question/SKILL.md +16 -13
- package/src/claude/skills/research/SKILL.md +70 -43
- package/src/claude/skills/route/SKILL.md +94 -0
- package/src/claude/skills/route/references/agent-timing.md +46 -0
- package/src/claude/skills/route/references/chaining-patterns.md +53 -0
- package/src/claude/skills/route/references/task-taxonomy.md +48 -0
- package/src/claude/skills/specs/SKILL.md +48 -57
- package/src/claude/skills/specs/references/review.md +26 -8
- package/src/claude/skills/specs/references/templates.md +76 -31
- package/src/claude/skills/specs/templates/requirements-init.md +1 -1
- package/src/claude/skills/specs/templates/research.md +2 -2
- package/src/claude/skills/sync/SKILL.md +6 -6
- package/src/claude/skills/sync/references/sync-protocols.md +1 -1
- package/src/claude/skills/test/SKILL.md +120 -120
- package/src/claude/skills/test/references/execution-strategy.md +208 -361
- package/src/claude/skills/test/references/failure-triage.md +55 -121
- package/src/claude/skills/test/references/test-memory.md +20 -34
- package/src/claude/skills/ui-ux-pro-max/SKILL.md +1 -1
- package/src/claude/skills/web-testing/SKILL.md +1 -1
- package/src/claude/skills/xlsx/SKILL.md +1 -1
- package/src/claude/status.cjs +135 -33
- package/src/codex/AGENTS.md +14 -4
- package/src/codex/hooks/docs-sync.cjs +8 -3
- package/src/codex/hooks/lib/hook-context.cjs +18 -4
- package/src/codex/hooks/rules.cjs +29 -1
- package/src/codex/hooks/secret-output-guardrail.cjs +30 -0
- package/src/codex/hooks/spec-gate.cjs +3 -8
- package/src/codex/hooks/spec-state.cjs +34 -5
- package/src/codex/hooks.json +5 -0
- package/src/codex/runtime.json +2 -0
- package/src/common/.env.example +1 -1
- package/src/claude/skills/backend-development/SKILL.md +0 -101
- package/src/claude/skills/backend-development/references/backend-api-design.md +0 -495
- package/src/claude/skills/backend-development/references/backend-architecture.md +0 -454
- package/src/claude/skills/backend-development/references/backend-authentication.md +0 -338
- package/src/claude/skills/backend-development/references/backend-code-quality.md +0 -659
- package/src/claude/skills/backend-development/references/backend-debugging.md +0 -904
- package/src/claude/skills/backend-development/references/backend-devops.md +0 -491
- package/src/claude/skills/backend-development/references/backend-mindset.md +0 -387
- package/src/claude/skills/backend-development/references/backend-performance.md +0 -397
- package/src/claude/skills/backend-development/references/backend-security.md +0 -342
- package/src/claude/skills/backend-development/references/backend-technologies.md +0 -256
- package/src/claude/skills/backend-development/references/backend-testing.md +0 -429
- package/src/claude/skills/devops/.env.example +0 -76
- package/src/claude/skills/devops/SKILL.md +0 -102
- package/src/claude/skills/devops/references/browser-rendering.md +0 -305
- package/src/claude/skills/devops/references/cloudflare-d1-kv.md +0 -123
- package/src/claude/skills/devops/references/cloudflare-platform.md +0 -271
- package/src/claude/skills/devops/references/cloudflare-r2-storage.md +0 -280
- package/src/claude/skills/devops/references/cloudflare-workers-advanced.md +0 -312
- package/src/claude/skills/devops/references/cloudflare-workers-apis.md +0 -309
- package/src/claude/skills/devops/references/cloudflare-workers-basics.md +0 -418
- package/src/claude/skills/devops/references/docker-basics.md +0 -297
- package/src/claude/skills/devops/references/docker-compose.md +0 -292
- package/src/claude/skills/devops/references/gcloud-platform.md +0 -297
- package/src/claude/skills/devops/references/gcloud-services.md +0 -304
- package/src/claude/skills/devops/references/kubernetes-basics.md +0 -95
- package/src/claude/skills/devops/references/kubernetes-helm-advanced.md +0 -70
- package/src/claude/skills/devops/references/kubernetes-helm.md +0 -81
- package/src/claude/skills/devops/references/kubernetes-kubectl.md +0 -74
- package/src/claude/skills/devops/references/kubernetes-security-advanced.md +0 -94
- package/src/claude/skills/devops/references/kubernetes-security.md +0 -89
- package/src/claude/skills/devops/references/kubernetes-troubleshooting-advanced.md +0 -74
- package/src/claude/skills/devops/references/kubernetes-troubleshooting.md +0 -49
- package/src/claude/skills/devops/references/kubernetes-workflows-advanced.md +0 -75
- package/src/claude/skills/devops/references/kubernetes-workflows.md +0 -76
- package/src/claude/skills/devops/scripts/cloudflare_deploy.py +0 -269
- package/src/claude/skills/devops/scripts/docker_optimize.py +0 -332
- package/src/claude/skills/devops/scripts/requirements.txt +0 -20
- package/src/claude/skills/devops/scripts/tests/requirements.txt +0 -3
- package/src/claude/skills/devops/scripts/tests/test_cloudflare_deploy.py +0 -285
- package/src/claude/skills/devops/scripts/tests/test_docker_optimize.py +0 -436
- package/src/claude/skills/frontend-design/SKILL.md +0 -135
- package/src/claude/skills/frontend-design/references/ai-multimodal-overview.md +0 -165
- package/src/claude/skills/frontend-design/references/analysis-best-practices.md +0 -80
- package/src/claude/skills/frontend-design/references/analysis-prompts.md +0 -141
- package/src/claude/skills/frontend-design/references/analysis-techniques.md +0 -118
- package/src/claude/skills/frontend-design/references/animejs.md +0 -396
- package/src/claude/skills/frontend-design/references/anti-slop-rules.md +0 -103
- package/src/claude/skills/frontend-design/references/asset-generation.md +0 -337
- package/src/claude/skills/frontend-design/references/bento-motion-engine.md +0 -142
- package/src/claude/skills/frontend-design/references/design-extraction-overview.md +0 -71
- package/src/claude/skills/frontend-design/references/extraction-best-practices.md +0 -141
- package/src/claude/skills/frontend-design/references/extraction-output-templates.md +0 -162
- package/src/claude/skills/frontend-design/references/extraction-prompts.md +0 -127
- package/src/claude/skills/frontend-design/references/magicui-components.md +0 -129
- package/src/claude/skills/frontend-design/references/performance-guardrails.md +0 -169
- package/src/claude/skills/frontend-design/references/premium-design-patterns.md +0 -93
- package/src/claude/skills/frontend-design/references/redesign-audit-checklist.md +0 -114
- package/src/claude/skills/frontend-design/references/technical-accessibility.md +0 -119
- package/src/claude/skills/frontend-design/references/technical-best-practices.md +0 -97
- package/src/claude/skills/frontend-design/references/technical-optimization.md +0 -44
- package/src/claude/skills/frontend-design/references/technical-overview.md +0 -90
- package/src/claude/skills/frontend-design/references/technical-workflows.md +0 -150
- package/src/claude/skills/frontend-design/references/visual-analysis-overview.md +0 -95
- package/src/claude/skills/frontend-design/references/workflow-3d.md +0 -102
- package/src/claude/skills/frontend-design/references/workflow-describe.md +0 -87
- package/src/claude/skills/frontend-design/references/workflow-immersive.md +0 -87
- package/src/claude/skills/frontend-design/references/workflow-quick.md +0 -57
- package/src/claude/skills/frontend-design/references/workflow-screenshot.md +0 -63
- package/src/claude/skills/frontend-design/references/workflow-video.md +0 -74
- package/src/claude/skills/frontend-development/SKILL.md +0 -406
- package/src/claude/skills/frontend-development/resources/common-patterns.md +0 -331
- package/src/claude/skills/frontend-development/resources/complete-examples.md +0 -872
- package/src/claude/skills/frontend-development/resources/component-patterns.md +0 -502
- package/src/claude/skills/frontend-development/resources/data-fetching.md +0 -767
- package/src/claude/skills/frontend-development/resources/file-organization.md +0 -502
- package/src/claude/skills/frontend-development/resources/loading-and-error-states.md +0 -501
- package/src/claude/skills/frontend-development/resources/performance.md +0 -406
- package/src/claude/skills/frontend-development/resources/routing-guide.md +0 -364
- package/src/claude/skills/frontend-development/resources/styling-guide.md +0 -428
- package/src/claude/skills/frontend-development/resources/typescript-standards.md +0 -418
- package/src/claude/skills/mobile-development/SKILL.md +0 -218
- package/src/claude/skills/mobile-development/references/mobile-android.md +0 -604
- package/src/claude/skills/mobile-development/references/mobile-best-practices.md +0 -544
- package/src/claude/skills/mobile-development/references/mobile-debugging.md +0 -1089
- package/src/claude/skills/mobile-development/references/mobile-frameworks.md +0 -465
- package/src/claude/skills/mobile-development/references/mobile-ios.md +0 -496
- package/src/claude/skills/mobile-development/references/mobile-mindset.md +0 -542
- package/src/claude/skills/react-best-practices/AGENTS.md +0 -2249
- package/src/claude/skills/react-best-practices/README.md +0 -123
- package/src/claude/skills/react-best-practices/SKILL.md +0 -128
- package/src/claude/skills/react-best-practices/metadata.json +0 -15
- package/src/claude/skills/react-best-practices/rules/_sections.md +0 -46
- package/src/claude/skills/react-best-practices/rules/_template.md +0 -28
- package/src/claude/skills/react-best-practices/rules/advanced-event-handler-refs.md +0 -55
- package/src/claude/skills/react-best-practices/rules/advanced-use-latest.md +0 -49
- package/src/claude/skills/react-best-practices/rules/async-api-routes.md +0 -38
- package/src/claude/skills/react-best-practices/rules/async-defer-await.md +0 -80
- package/src/claude/skills/react-best-practices/rules/async-dependencies.md +0 -36
- package/src/claude/skills/react-best-practices/rules/async-parallel.md +0 -28
- package/src/claude/skills/react-best-practices/rules/async-suspense-boundaries.md +0 -99
- package/src/claude/skills/react-best-practices/rules/bundle-barrel-imports.md +0 -59
- package/src/claude/skills/react-best-practices/rules/bundle-conditional.md +0 -31
- package/src/claude/skills/react-best-practices/rules/bundle-defer-third-party.md +0 -49
- package/src/claude/skills/react-best-practices/rules/bundle-dynamic-imports.md +0 -35
- package/src/claude/skills/react-best-practices/rules/bundle-preload.md +0 -50
- package/src/claude/skills/react-best-practices/rules/client-event-listeners.md +0 -74
- package/src/claude/skills/react-best-practices/rules/client-swr-dedup.md +0 -56
- package/src/claude/skills/react-best-practices/rules/js-batch-dom-css.md +0 -82
- package/src/claude/skills/react-best-practices/rules/js-cache-function-results.md +0 -80
- package/src/claude/skills/react-best-practices/rules/js-cache-property-access.md +0 -28
- package/src/claude/skills/react-best-practices/rules/js-cache-storage.md +0 -70
- package/src/claude/skills/react-best-practices/rules/js-combine-iterations.md +0 -32
- package/src/claude/skills/react-best-practices/rules/js-early-exit.md +0 -50
- package/src/claude/skills/react-best-practices/rules/js-hoist-regexp.md +0 -45
- package/src/claude/skills/react-best-practices/rules/js-index-maps.md +0 -37
- package/src/claude/skills/react-best-practices/rules/js-length-check-first.md +0 -49
- package/src/claude/skills/react-best-practices/rules/js-min-max-loop.md +0 -82
- package/src/claude/skills/react-best-practices/rules/js-set-map-lookups.md +0 -24
- package/src/claude/skills/react-best-practices/rules/js-tosorted-immutable.md +0 -57
- package/src/claude/skills/react-best-practices/rules/rendering-activity.md +0 -26
- package/src/claude/skills/react-best-practices/rules/rendering-animate-svg-wrapper.md +0 -47
- package/src/claude/skills/react-best-practices/rules/rendering-conditional-render.md +0 -40
- package/src/claude/skills/react-best-practices/rules/rendering-content-visibility.md +0 -38
- package/src/claude/skills/react-best-practices/rules/rendering-hoist-jsx.md +0 -46
- package/src/claude/skills/react-best-practices/rules/rendering-hydration-no-flicker.md +0 -82
- package/src/claude/skills/react-best-practices/rules/rendering-svg-precision.md +0 -28
- package/src/claude/skills/react-best-practices/rules/rerender-defer-reads.md +0 -39
- package/src/claude/skills/react-best-practices/rules/rerender-dependencies.md +0 -45
- package/src/claude/skills/react-best-practices/rules/rerender-derived-state.md +0 -29
- package/src/claude/skills/react-best-practices/rules/rerender-functional-setstate.md +0 -74
- package/src/claude/skills/react-best-practices/rules/rerender-lazy-state-init.md +0 -58
- package/src/claude/skills/react-best-practices/rules/rerender-memo.md +0 -44
- package/src/claude/skills/react-best-practices/rules/rerender-transitions.md +0 -40
- package/src/claude/skills/react-best-practices/rules/server-after-nonblocking.md +0 -73
- package/src/claude/skills/react-best-practices/rules/server-cache-lru.md +0 -41
- package/src/claude/skills/react-best-practices/rules/server-cache-react.md +0 -26
- package/src/claude/skills/react-best-practices/rules/server-parallel-fetching.md +0 -79
- package/src/claude/skills/react-best-practices/rules/server-serialization.md +0 -38
|
@@ -1,184 +1,218 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* This mirrors the Research/.claude approach: expose the installed skill set as
|
|
6
|
-
* a readable catalog so the model can choose skills from rules, not from an
|
|
7
|
-
* automatic prompt-scoring hook.
|
|
8
|
-
*/
|
|
2
|
+
/** Generate a fail-closed catalog from the current runtime's installed skills. */
|
|
3
|
+
|
|
4
|
+
'use strict';
|
|
9
5
|
|
|
10
6
|
const fs = require('fs');
|
|
11
7
|
const path = require('path');
|
|
12
8
|
|
|
9
|
+
// Keep synchronized with migration-manifest.json obsolete.skills. Modified
|
|
10
|
+
// retired directories may remain on disk, but they are never automatic routes.
|
|
11
|
+
const RETIRED_DIRECTORIES = new Set([
|
|
12
|
+
'backend-development',
|
|
13
|
+
'frontend-development',
|
|
14
|
+
'frontend-design',
|
|
15
|
+
'mobile-development',
|
|
16
|
+
'devops',
|
|
17
|
+
'react-best-practices',
|
|
18
|
+
]);
|
|
19
|
+
const PUBLIC_SUFFIX_BY_DIRECTORY = {
|
|
20
|
+
hotfix: 'fix',
|
|
21
|
+
inspect: 'scout',
|
|
22
|
+
question: 'ask',
|
|
23
|
+
};
|
|
24
|
+
|
|
13
25
|
function usage() {
|
|
14
26
|
console.log(`Usage:
|
|
15
|
-
node
|
|
27
|
+
node <runtime>/scripts/generate-skill-catalog.cjs [--skills] [--json] [--root <skills-dir>]
|
|
16
28
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
node .claude/scripts/generate-skill-catalog.cjs --json --root .claude/skills`);
|
|
29
|
+
The default root is bound to the script runtime: .claude/skills for Claude,
|
|
30
|
+
.agents/skills for Codex, and ../skills for the source tree.`);
|
|
20
31
|
}
|
|
21
32
|
|
|
22
33
|
function parseArgs(argv) {
|
|
23
34
|
const args = { json: false, help: false, root: null };
|
|
24
|
-
for (let
|
|
25
|
-
const arg = argv[
|
|
35
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
36
|
+
const arg = argv[index];
|
|
26
37
|
if (arg === '--help' || arg === '-h') args.help = true;
|
|
27
38
|
else if (arg === '--json') args.json = true;
|
|
28
39
|
else if (arg === '--skills') continue;
|
|
29
40
|
else if (arg === '--root') {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
args.root =
|
|
34
|
-
}
|
|
41
|
+
if (!argv[index + 1] || argv[index + 1].startsWith('--')) {
|
|
42
|
+
throw new Error('--root requires a skills directory');
|
|
43
|
+
}
|
|
44
|
+
args.root = argv[++index];
|
|
45
|
+
} else throw new Error(`unknown option: ${arg}`);
|
|
35
46
|
}
|
|
36
47
|
return args;
|
|
37
48
|
}
|
|
38
49
|
|
|
39
50
|
function defaultSkillsRoot() {
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
return candidates.find((candidate) => fs.existsSync(candidate)) || candidates[0];
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function readText(filePath) {
|
|
50
|
-
return fs.readFileSync(filePath, 'utf8');
|
|
51
|
+
const runtimeRoot = path.dirname(__dirname);
|
|
52
|
+
const runtimeName = path.basename(runtimeRoot);
|
|
53
|
+
if (runtimeName === '.codex') return path.join(path.dirname(runtimeRoot), '.agents', 'skills');
|
|
54
|
+
const claudeRuntimeName = `.${['cla', 'ude'].join('')}`;
|
|
55
|
+
if (runtimeName === claudeRuntimeName) return path.join(runtimeRoot, 'skills');
|
|
56
|
+
return path.resolve(__dirname, '..', 'skills');
|
|
51
57
|
}
|
|
52
58
|
|
|
53
59
|
function extractFrontmatter(content) {
|
|
54
|
-
const match = content.match(/^---\
|
|
55
|
-
if (!match) return
|
|
60
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/);
|
|
61
|
+
if (!match) return null;
|
|
56
62
|
const frontmatter = {};
|
|
57
63
|
const lines = match[1].split(/\r?\n/);
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
64
|
+
const seen = new Set();
|
|
65
|
+
let nestedKey = null;
|
|
66
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
67
|
+
const line = lines[index];
|
|
68
|
+
if (line.trim() === '') continue;
|
|
69
|
+
if (/^\s+/.test(line)) {
|
|
70
|
+
const nested = line.match(/^ ([A-Za-z0-9_-]+):\s*(\S.*)$/);
|
|
71
|
+
if (!nestedKey || !nested) return null;
|
|
72
|
+
const identity = `${nestedKey}.${nested[1]}`;
|
|
73
|
+
if (seen.has(identity) || quotedValue(nested[2]) === null) return null;
|
|
74
|
+
seen.add(identity);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
nestedKey = null;
|
|
78
|
+
const parsed = line.match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
|
|
79
|
+
if (!parsed || seen.has(parsed[1])) return null;
|
|
80
|
+
seen.add(parsed[1]);
|
|
81
|
+
let value = parsed[2].trim();
|
|
82
|
+
if (['>', '>-', '|', '|-'].includes(value)) {
|
|
65
83
|
const block = [];
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
block.push(lines[i].trim());
|
|
69
|
-
i += 1;
|
|
84
|
+
while (index + 1 < lines.length && /^\s+/.test(lines[index + 1])) {
|
|
85
|
+
block.push(lines[++index].trim());
|
|
70
86
|
}
|
|
71
|
-
|
|
87
|
+
if (block.length === 0) return null;
|
|
72
88
|
value = block.join(' ');
|
|
73
89
|
}
|
|
74
|
-
if (
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
value = value.slice(1, -1);
|
|
90
|
+
if (value === '') {
|
|
91
|
+
nestedKey = parsed[1];
|
|
92
|
+
frontmatter[parsed[1]] = '';
|
|
93
|
+
continue;
|
|
79
94
|
}
|
|
80
|
-
|
|
95
|
+
value = quotedValue(value);
|
|
96
|
+
if (value === null) return null;
|
|
97
|
+
frontmatter[parsed[1]] = value;
|
|
81
98
|
}
|
|
82
99
|
return frontmatter;
|
|
83
100
|
}
|
|
84
101
|
|
|
85
|
-
function
|
|
86
|
-
const
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
94
|
-
paragraph.push(line);
|
|
95
|
-
if (paragraph.join(' ').length > 160) break;
|
|
102
|
+
function quotedValue(value) {
|
|
103
|
+
const startsQuote = value.startsWith('"') || value.startsWith("'");
|
|
104
|
+
const endsQuote = value.endsWith('"') || value.endsWith("'");
|
|
105
|
+
const startsFlow = value.startsWith('[') || value.startsWith('{');
|
|
106
|
+
const endsFlow = value.endsWith(']') || value.endsWith('}');
|
|
107
|
+
if (startsFlow || endsFlow) {
|
|
108
|
+
const expected = value.startsWith('[') ? ']' : value.startsWith('{') ? '}' : null;
|
|
109
|
+
if (!expected || !value.endsWith(expected)) return null;
|
|
96
110
|
}
|
|
97
|
-
|
|
111
|
+
if (!startsQuote && !endsQuote) return value;
|
|
112
|
+
if (value.length < 2 || value[0] !== value[value.length - 1]) return null;
|
|
113
|
+
return value.slice(1, -1);
|
|
98
114
|
}
|
|
99
115
|
|
|
100
|
-
function
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if (/debug|hotfix|inspect|impact|review|test|agent-browser|chrome/.test(text)) return 'quality';
|
|
104
|
-
if (/frontend|react|ui|ux|mobile/.test(text)) return 'frontend-mobile';
|
|
105
|
-
if (/pdf|docx|pptx|xlsx|multimodal/.test(text)) return 'artifacts-tools';
|
|
106
|
-
if (/backend|devops|api|database|deploy/.test(text)) return 'backend-infra';
|
|
107
|
-
if (/docs|research|graph/.test(text)) return 'knowledge';
|
|
108
|
-
if (/git/.test(text)) return 'git';
|
|
109
|
-
return 'other';
|
|
116
|
+
function parseKeywords(value) {
|
|
117
|
+
if (!value || !/^\[[^\]]*\]$/.test(value)) return [];
|
|
118
|
+
return value.slice(1, -1).split(',').map((item) => item.trim()).filter(Boolean);
|
|
110
119
|
}
|
|
111
120
|
|
|
112
|
-
function
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
121
|
+
function publicIdentity(name) {
|
|
122
|
+
const match = String(name).match(/^cf(?::|-)([a-z0-9]+(?:-[a-z0-9]+)*)$/);
|
|
123
|
+
return match ? `cf:${match[1]}` : null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function expectedIdentity(directory) {
|
|
127
|
+
return `cf:${PUBLIC_SUFFIX_BY_DIRECTORY[directory] || directory}`;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function diagnostic(code, directory, message) {
|
|
131
|
+
return { code, directory, message };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function scanSkills(requestedRoot) {
|
|
135
|
+
if (!fs.existsSync(requestedRoot)) throw new Error(`skills root not found: ${requestedRoot}`);
|
|
136
|
+
const root = fs.realpathSync(requestedRoot);
|
|
137
|
+
const diagnostics = [];
|
|
138
|
+
const candidates = [];
|
|
139
|
+
const entries = fs.readdirSync(root, { withFileTypes: true })
|
|
140
|
+
.filter((entry) => entry.isDirectory() && !entry.name.startsWith('.'))
|
|
141
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
142
|
+
|
|
118
143
|
for (const entry of entries) {
|
|
119
|
-
if (!entry.isDirectory() || entry.name.startsWith('.')) continue;
|
|
120
144
|
const skillPath = path.join(root, entry.name, 'SKILL.md');
|
|
121
145
|
if (!fs.existsSync(skillPath)) continue;
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
146
|
+
if (RETIRED_DIRECTORIES.has(entry.name)) {
|
|
147
|
+
diagnostics.push(diagnostic('retired_skill', entry.name, 'excluded from automatic routing'));
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
const frontmatter = extractFrontmatter(fs.readFileSync(skillPath, 'utf8'));
|
|
151
|
+
if (!frontmatter) {
|
|
152
|
+
diagnostics.push(diagnostic('malformed_frontmatter', entry.name, 'missing frontmatter block'));
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
const identity = publicIdentity(frontmatter.name);
|
|
156
|
+
if (!identity || !frontmatter.description || frontmatter.description.trim() === '') {
|
|
157
|
+
diagnostics.push(diagnostic('missing_routing_metadata', entry.name, 'requires valid name and description'));
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (identity !== expectedIdentity(entry.name)) {
|
|
161
|
+
diagnostics.push(diagnostic('folder_name_mismatch', entry.name, `${identity} does not match directory`));
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
candidates.push({
|
|
165
|
+
name: frontmatter.name,
|
|
166
|
+
public_id: identity,
|
|
128
167
|
directory: entry.name,
|
|
129
|
-
description,
|
|
130
|
-
|
|
168
|
+
description: frontmatter.description,
|
|
169
|
+
when_to_use: frontmatter.when_to_use || '',
|
|
170
|
+
category: frontmatter.category || 'other',
|
|
171
|
+
keywords: parseKeywords(frontmatter.keywords),
|
|
172
|
+
user_invocable: frontmatter['user-invocable'] !== 'false',
|
|
131
173
|
has_references: fs.existsSync(path.join(root, entry.name, 'references')),
|
|
132
174
|
has_scripts: fs.existsSync(path.join(root, entry.name, 'scripts')),
|
|
133
175
|
});
|
|
134
176
|
}
|
|
135
|
-
|
|
177
|
+
|
|
178
|
+
const counts = new Map();
|
|
179
|
+
for (const skill of candidates) counts.set(skill.public_id, (counts.get(skill.public_id) || 0) + 1);
|
|
180
|
+
const skills = candidates.filter((skill) => {
|
|
181
|
+
if (counts.get(skill.public_id) === 1) return true;
|
|
182
|
+
diagnostics.push(diagnostic('duplicate_public_name', skill.directory, `${skill.public_id} requires explicit user disambiguation`));
|
|
183
|
+
return false;
|
|
184
|
+
}).sort((left, right) => left.public_id.localeCompare(right.public_id));
|
|
185
|
+
|
|
186
|
+
return { root, skills, diagnostics: diagnostics.sort((left, right) =>
|
|
187
|
+
`${left.directory}:${left.code}`.localeCompare(`${right.directory}:${right.code}`)) };
|
|
136
188
|
}
|
|
137
189
|
|
|
138
|
-
function renderMarkdown(
|
|
139
|
-
const lines = [
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
`
|
|
143
|
-
|
|
144
|
-
'',
|
|
145
|
-
];
|
|
146
|
-
const categories = [...new Set(skills.map((skill) => skill.category))].sort();
|
|
147
|
-
for (const category of categories) {
|
|
148
|
-
lines.push(`## ${category}`, '');
|
|
149
|
-
for (const skill of skills.filter((item) => item.category === category)) {
|
|
150
|
-
const badges = [
|
|
151
|
-
skill.has_references ? 'refs' : null,
|
|
152
|
-
skill.has_scripts ? 'scripts' : null,
|
|
153
|
-
].filter(Boolean);
|
|
154
|
-
lines.push(
|
|
155
|
-
`- \`${skill.name}\` (${skill.directory})${badges.length ? ` [${badges.join(', ')}]` : ''}: ${skill.description}`
|
|
156
|
-
);
|
|
157
|
-
}
|
|
158
|
-
lines.push('');
|
|
190
|
+
function renderMarkdown(catalog) {
|
|
191
|
+
const lines = ['# CafeKit Skills Catalog', '', `Skills root: \`${catalog.root}\``,
|
|
192
|
+
`Routable skills: ${catalog.skills.length}`, ''];
|
|
193
|
+
for (const skill of catalog.skills) {
|
|
194
|
+
const intent = skill.when_to_use ? ` Use: ${skill.when_to_use}` : '';
|
|
195
|
+
lines.push(`- \`${skill.name}\` (${skill.directory}, ${skill.category}): ${skill.description}${intent}`);
|
|
159
196
|
}
|
|
160
|
-
|
|
197
|
+
if (catalog.diagnostics.length > 0) {
|
|
198
|
+
lines.push('', '## Diagnostics', '');
|
|
199
|
+
for (const item of catalog.diagnostics) lines.push(`- ${item.code} (${item.directory}): ${item.message}`);
|
|
200
|
+
}
|
|
201
|
+
return `${lines.join('\n').trim()}\n`;
|
|
161
202
|
}
|
|
162
203
|
|
|
163
204
|
function main() {
|
|
164
205
|
const args = parseArgs(process.argv.slice(2));
|
|
165
|
-
if (args.help)
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const root = path.resolve(process.cwd(), args.root || defaultSkillsRoot());
|
|
171
|
-
const skills = scanSkills(root);
|
|
172
|
-
if (args.json) {
|
|
173
|
-
console.log(JSON.stringify({ root, total: skills.length, skills }, null, 2));
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
console.log(renderMarkdown(skills, root));
|
|
206
|
+
if (args.help) return usage();
|
|
207
|
+
const requestedRoot = path.resolve(process.cwd(), args.root || defaultSkillsRoot());
|
|
208
|
+
const catalog = scanSkills(requestedRoot);
|
|
209
|
+
if (args.json) console.log(JSON.stringify({ ...catalog, total: catalog.skills.length }, null, 2));
|
|
210
|
+
else console.log(renderMarkdown(catalog));
|
|
177
211
|
}
|
|
178
212
|
|
|
179
213
|
try {
|
|
180
214
|
main();
|
|
181
215
|
} catch (error) {
|
|
182
216
|
console.error(`[skill-catalog] ${error.message}`);
|
|
183
|
-
process.
|
|
217
|
+
process.exitCode = 1;
|
|
184
218
|
}
|
|
@@ -83,9 +83,22 @@ function oneLine(value, label) {
|
|
|
83
83
|
return output.replace(/\n$/, '');
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
function gitBase(root) {
|
|
87
|
-
const
|
|
88
|
-
|
|
86
|
+
function gitBase(root, specsRoot) {
|
|
87
|
+
const specsRelative = path.relative(root, specsRoot).split(path.sep).join('/');
|
|
88
|
+
const sourceHistory = decode(runGit(
|
|
89
|
+
root,
|
|
90
|
+
['log', '-1', '--format=%H', '--', '.', `:(exclude,literal)${specsRelative}`],
|
|
91
|
+
'git log source base',
|
|
92
|
+
)).trim();
|
|
93
|
+
const roots = sourceHistory ? [] : decode(
|
|
94
|
+
runGit(root, ['rev-list', '--max-parents=0', '--reverse', 'HEAD'], 'git rev-list root'),
|
|
95
|
+
'git rev-list root',
|
|
96
|
+
).trim().split('\n');
|
|
97
|
+
if (!sourceHistory && (roots.length === 0 || roots.some((rootId) => !/^[a-f0-9]{40}(?:[a-f0-9]{24})?$/i.test(rootId)))) {
|
|
98
|
+
fail('malformed_git_output', 'git rev-list root did not return commit ids');
|
|
99
|
+
}
|
|
100
|
+
const output = (sourceHistory || roots[0]).toLowerCase();
|
|
101
|
+
if (!/^[a-f0-9]{40}(?:[a-f0-9]{24})?$/.test(output)) fail('malformed_git_output', 'git source base did not return a commit id');
|
|
89
102
|
return output;
|
|
90
103
|
}
|
|
91
104
|
|
|
@@ -211,11 +224,11 @@ function identity(input) {
|
|
|
211
224
|
|
|
212
225
|
function deriveRuntimeProvenance(input) {
|
|
213
226
|
const current = identity(input);
|
|
214
|
-
const base = gitBase(current.root);
|
|
227
|
+
const base = gitBase(current.root, current.specsRoot);
|
|
215
228
|
const first = manifest(current.root, current.specsRoot);
|
|
216
229
|
const head = manifestDigest(first);
|
|
217
230
|
const second = manifest(current.root, current.specsRoot);
|
|
218
|
-
if (JSON.stringify(first) !== JSON.stringify(second) || gitBase(current.root) !== base) fail('worktree_race', 'checkout changed during provenance capture');
|
|
231
|
+
if (JSON.stringify(first) !== JSON.stringify(second) || gitBase(current.root, current.specsRoot) !== base) fail('worktree_race', 'checkout changed during provenance capture');
|
|
219
232
|
const stable = {
|
|
220
233
|
schema_version: CONTEXT_SCHEMA_VERSION,
|
|
221
234
|
project_root: current.root,
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const crypto = require('crypto');
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const path = require('path');
|
|
6
|
+
const { annotatedMarkdownLines } = require('./spec-resolver.cjs');
|
|
6
7
|
|
|
7
8
|
const EVIDENCE_HEADINGS = new Set([
|
|
8
9
|
'Evidence',
|
|
@@ -67,21 +68,46 @@ function evidenceBody(text, policy = {}) {
|
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
function workflowReceiptBody(text, policy = {}) {
|
|
70
|
-
const lines =
|
|
71
|
+
const lines = annotatedMarkdownLines(text);
|
|
71
72
|
const starts = [];
|
|
72
73
|
for (let index = 0; index < lines.length; index += 1) {
|
|
73
|
-
if (/^##\s+Receipt\s*$/.test(lines[index])) starts.push(index + 1);
|
|
74
|
+
if (lines[index].outsideFence && /^##\s+Receipt\s*$/.test(lines[index].line)) starts.push(index + 1);
|
|
74
75
|
}
|
|
75
76
|
if (starts.length !== 1) return null;
|
|
76
77
|
|
|
77
78
|
const start = starts[0];
|
|
78
79
|
let end = lines.length;
|
|
79
80
|
for (let index = start; index < lines.length; index += 1) {
|
|
80
|
-
|
|
81
|
-
const
|
|
81
|
+
if (!lines[index].outsideFence) continue;
|
|
82
|
+
const heading = lines[index].line.match(/^(#{1,6})\s+/);
|
|
83
|
+
const tap = typeof policy.isTapMetadataHeading === 'function' && policy.isTapMetadataHeading(lines[index].line);
|
|
82
84
|
if (heading && heading[1].length <= 2 && !tap) { end = index; break; }
|
|
83
85
|
}
|
|
84
|
-
return lines.slice(start, end).join('\n');
|
|
86
|
+
return lines.slice(start, end).map(({ line }) => line).join('\n');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function workflowCanonicalValidationBody(body) {
|
|
90
|
+
return annotatedMarkdownLines(body)
|
|
91
|
+
.filter(({ outsideFence }) => outsideFence)
|
|
92
|
+
.map(({ line }) => line)
|
|
93
|
+
.join('\n');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function hasNonEmptyFencedBlock(body) {
|
|
97
|
+
let open = false;
|
|
98
|
+
let nonEmpty = false;
|
|
99
|
+
for (const { line, fenceEvent } of annotatedMarkdownLines(body)) {
|
|
100
|
+
if (fenceEvent === 'open') {
|
|
101
|
+
open = true;
|
|
102
|
+
nonEmpty = false;
|
|
103
|
+
} else if (fenceEvent === 'close') {
|
|
104
|
+
if (open && nonEmpty) return true;
|
|
105
|
+
open = false;
|
|
106
|
+
} else if (open && line.trim() !== '') {
|
|
107
|
+
nonEmpty = true;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return false;
|
|
85
111
|
}
|
|
86
112
|
|
|
87
113
|
function fieldValues(body, name) {
|
|
@@ -105,22 +131,14 @@ function normalizedCommand(value) {
|
|
|
105
131
|
function workflowVerificationCommand(taskText) {
|
|
106
132
|
const sections = [];
|
|
107
133
|
let current = null;
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if (marker) {
|
|
112
|
-
const kind = marker[1][0];
|
|
113
|
-
if (fence === null) fence = kind;
|
|
114
|
-
else if (fence === kind) fence = null;
|
|
115
|
-
continue;
|
|
116
|
-
}
|
|
117
|
-
if (fence !== null) continue;
|
|
118
|
-
if (fence === null && /^##\s+Verification Plan\s*$/.test(line)) {
|
|
134
|
+
for (const { line, outsideFence } of annotatedMarkdownLines(taskText)) {
|
|
135
|
+
if (!outsideFence) continue;
|
|
136
|
+
if (/^##\s+Verification Plan\s*$/.test(line)) {
|
|
119
137
|
current = [];
|
|
120
138
|
sections.push(current);
|
|
121
139
|
continue;
|
|
122
140
|
}
|
|
123
|
-
if (
|
|
141
|
+
if (current && /^#{1,2}\s+/.test(line)) {
|
|
124
142
|
current = null;
|
|
125
143
|
continue;
|
|
126
144
|
}
|
|
@@ -227,7 +245,10 @@ function checkWorkflowTaskReceipt(featureDir, taskPath, runtimeContext, policy)
|
|
|
227
245
|
}
|
|
228
246
|
|
|
229
247
|
const taskText = taskFile.bytes.toString('utf8');
|
|
230
|
-
const statusLines =
|
|
248
|
+
const statusLines = annotatedMarkdownLines(taskText)
|
|
249
|
+
.filter(({ outsideFence }) => outsideFence)
|
|
250
|
+
.map(({ line }) => line.match(/^\s*(?:\*\*)?Status(?:\*\*)?\s*:\s*(?:\*\*)?\s*([A-Za-z_-]+)(?:\*\*)?\s*$/i))
|
|
251
|
+
.filter(Boolean);
|
|
231
252
|
const body = workflowReceiptBody(taskText, policy);
|
|
232
253
|
const failures = [];
|
|
233
254
|
if (statusLines.length !== 1 || statusLines[0][1].toLowerCase().replaceAll('-', '_') !== 'done') {
|
|
@@ -236,12 +257,15 @@ function checkWorkflowTaskReceipt(featureDir, taskPath, runtimeContext, policy)
|
|
|
236
257
|
if (body === null) {
|
|
237
258
|
failures.push('missing_receipt');
|
|
238
259
|
} else {
|
|
260
|
+
const canonicalBody = workflowCanonicalValidationBody(body);
|
|
239
261
|
const options = typeof policy?.receiptValidatorOptions === 'function'
|
|
240
262
|
? policy.receiptValidatorOptions({}, { runtimeContext, requireProvenanceBinding: true })
|
|
241
263
|
: { requireProvenanceBinding: true };
|
|
242
|
-
failures.push(...canonicalFailures(
|
|
243
|
-
failures.push(...
|
|
244
|
-
|
|
264
|
+
failures.push(...canonicalFailures(canonicalBody, {}, runtimeContext, policy));
|
|
265
|
+
failures.push(...canonicalFailures(body, {}, runtimeContext, policy)
|
|
266
|
+
.filter((failure) => ['placeholder', 'verification_state', 'exit_result'].includes(failure)));
|
|
267
|
+
failures.push(...workflowCommandFailures(taskText, canonicalBody));
|
|
268
|
+
if (!hasNonEmptyFencedBlock(body)) failures.push('command_output');
|
|
245
269
|
if (!options.expectedProvenance) failures.push('provenance');
|
|
246
270
|
}
|
|
247
271
|
return {
|
|
@@ -256,6 +280,36 @@ function checkWorkflowTaskReceipt(featureDir, taskPath, runtimeContext, policy)
|
|
|
256
280
|
};
|
|
257
281
|
}
|
|
258
282
|
|
|
283
|
+
function workflowProofSignature(taskPath, proof) {
|
|
284
|
+
const hash = crypto.createHash('sha256');
|
|
285
|
+
hash.update('cafekit-workflow-proof-v1\0');
|
|
286
|
+
hash.update(String(taskPath));
|
|
287
|
+
hash.update('\0');
|
|
288
|
+
hash.update(proof.taskFile?.bytes || Buffer.alloc(0));
|
|
289
|
+
hash.update('\0');
|
|
290
|
+
hash.update(proof.receiptBytes || Buffer.alloc(0));
|
|
291
|
+
hash.update('\0');
|
|
292
|
+
hash.update(String(proof.status || 'unknown'));
|
|
293
|
+
return hash.digest('hex');
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function workflowDependencyProofState(featureDir, taskRegistry, runtimeContext, policy) {
|
|
297
|
+
const tasks = {};
|
|
298
|
+
for (const taskPath of Object.keys(taskRegistry || {}).sort()) {
|
|
299
|
+
const done = taskRegistry[taskPath]?.status === 'done';
|
|
300
|
+
const proof = checkWorkflowTaskReceipt(featureDir, taskPath, runtimeContext, policy);
|
|
301
|
+
const valid = proof.failures.length === 0;
|
|
302
|
+
tasks[taskPath] = {
|
|
303
|
+
done,
|
|
304
|
+
valid,
|
|
305
|
+
eligible: done && valid,
|
|
306
|
+
signature: workflowProofSignature(taskPath, proof),
|
|
307
|
+
failures: [...proof.failures],
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
return tasks;
|
|
311
|
+
}
|
|
312
|
+
|
|
259
313
|
function checkWorkflowReceiptSet(candidates, projectRoot, runtimeSession, policy) {
|
|
260
314
|
const failures = [];
|
|
261
315
|
for (const candidate of Array.isArray(candidates) ? candidates : []) {
|
|
@@ -298,5 +352,6 @@ module.exports = {
|
|
|
298
352
|
evidenceBody,
|
|
299
353
|
readTaskProof,
|
|
300
354
|
safeRead,
|
|
355
|
+
workflowDependencyProofState,
|
|
301
356
|
workflowReceiptBody,
|
|
302
357
|
};
|