@sushanglewis/lincoln 1.5.0 → 1.5.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/dist/main.d.ts +3 -0
- package/dist/main.js +1231 -906
- package/dist/main.js.map +1 -1
- package/dist/postinstall.d.ts +3 -0
- package/dist/postinstall.js +18 -0
- package/dist/postinstall.js.map +1 -0
- package/framework/.claude/agents/_contract.md +79 -0
- package/framework/.claude/agents/architect.md +31 -0
- package/framework/.claude/agents/default.md +151 -0
- package/framework/.claude/agents/designer.md +33 -0
- package/framework/.claude/agents/engineer.md +42 -0
- package/framework/.claude/agents/external/NOTICES.md +12 -0
- package/framework/.claude/agents/external/everything-claude-code.manifest.yaml +23 -0
- package/framework/.claude/agents/external/oh-my-claudecode.manifest.yaml +26 -0
- package/framework/.claude/agents/external/wshobson-agents.manifest.yaml +23 -0
- package/framework/.claude/agents/frontend-engineer.md +32 -0
- package/framework/.claude/agents/knowledge-curator.md +28 -0
- package/framework/.claude/agents/pm.md +38 -0
- package/framework/.claude/agents/qa.md +28 -0
- package/framework/.claude/agents/release-coordinator.md +25 -0
- package/framework/.claude/agents/researcher.md +41 -0
- package/framework/.claude/harnesses/claude-code.yaml +15 -0
- package/framework/.claude/harnesses/codex.yaml +40 -0
- package/framework/.claude/harnesses/command-map.yaml +255 -0
- package/framework/.claude/harnesses/opencode.yaml +32 -0
- package/framework/.claude/harnesses/scenarios.yaml +45 -0
- package/framework/.claude/hooks/on-pr-event.sh +69 -0
- package/framework/.claude/hooks/on-session-start.sh +427 -0
- package/framework/.claude/hooks/on-stop.sh +104 -0
- package/framework/.claude/hooks/post-tool-use.sh +161 -0
- package/framework/.claude/hooks/pre-tool-use.sh +246 -0
- package/framework/.claude/schemas/agent-definition.schema.json +35 -0
- package/framework/.claude/schemas/benchmark-report.schema.json +148 -0
- package/framework/.claude/schemas/pm-to-ux-handoff.schema.yaml +128 -0
- package/framework/.claude/schemas/stage-definition.schema.json +233 -0
- package/framework/.claude/schemas/workflow-stage.schema.json +86 -0
- package/framework/.claude/schemas/workflow-template.schema.json +88 -0
- package/framework/.claude/settings.json +14 -0
- package/framework/.claude/skills/build-product-prototype/SKILL.md +35 -0
- package/framework/.claude/skills/build-product-prototype/prompts/main.md +78 -0
- package/framework/.claude/skills/clarify-requirements/SKILL.md +32 -0
- package/framework/.claude/skills/clarify-requirements/prompts/main.md +81 -0
- package/framework/.claude/skills/dependencies.yaml +139 -0
- package/framework/.claude/skills/draft-product-design/SKILL.md +40 -0
- package/framework/.claude/skills/draft-product-design/prompts/main.md +47 -0
- package/framework/.claude/skills/lc-analyze-frameworks/SKILL.md +43 -0
- package/framework/.claude/skills/lc-analyze-frameworks/prompts/main.md +95 -0
- package/framework/.claude/skills/lc-benchmark/SKILL.md +61 -0
- package/framework/.claude/skills/lc-build-codebase-knowledge/SKILL.md +38 -0
- package/framework/.claude/skills/lc-build-codebase-knowledge/prompts/build-codebase-knowledge.md +61 -0
- package/framework/.claude/skills/lc-collect-intelligence/SKILL.md +43 -0
- package/framework/.claude/skills/lc-collect-intelligence/prompts/main.md +72 -0
- package/framework/.claude/skills/lc-competitive-analysis/SKILL.md +43 -0
- package/framework/.claude/skills/lc-competitive-analysis/prompts/main.md +70 -0
- package/framework/.claude/skills/lc-explore-opensource/SKILL.md +36 -0
- package/framework/.claude/skills/lc-explore-opensource/prompts/explore-opensource.md +77 -0
- package/framework/.claude/skills/lc-first-principles/SKILL.md +43 -0
- package/framework/.claude/skills/lc-first-principles/prompts/main.md +73 -0
- package/framework/.claude/skills/lc-handoff/SKILL.md +34 -0
- package/framework/.claude/skills/lc-init-branch/SKILL.md +52 -0
- package/framework/.claude/skills/lc-market-research/SKILL.md +43 -0
- package/framework/.claude/skills/lc-market-research/prompts/main.md +79 -0
- package/framework/.claude/skills/lc-product-research/SKILL.md +43 -0
- package/framework/.claude/skills/lc-product-research/prompts/main.md +80 -0
- package/framework/.claude/skills/lc-research-report/SKILL.md +43 -0
- package/framework/.claude/skills/lc-research-report/prompts/main.md +90 -0
- package/framework/.claude/skills/lc-research-scope/SKILL.md +43 -0
- package/framework/.claude/skills/lc-research-scope/prompts/main.md +82 -0
- package/framework/.claude/skills/lc-setup/SKILL.md +68 -0
- package/framework/.claude/skills/lc-setup/prompts/main.md +88 -0
- package/framework/.claude/skills/lc-stage/SKILL.md +45 -0
- package/framework/.claude/skills/lc-stakeholder-research/SKILL.md +43 -0
- package/framework/.claude/skills/lc-stakeholder-research/prompts/main.md +65 -0
- package/framework/.claude/skills/lc-status/SKILL.md +25 -0
- package/framework/.claude/skills/lc-storytelling/SKILL.md +43 -0
- package/framework/.claude/skills/lc-storytelling/prompts/main.md +77 -0
- package/framework/.claude/skills/lc-wf/SKILL.md +54 -0
- package/framework/.claude/skills/lc-workflow/SKILL.md +48 -0
- package/framework/.claude/skills/lc-workflow/validators/validate.py +755 -0
- package/framework/.claude/skills/lc-workflow-router/SKILL.md +41 -0
- package/framework/.claude/skills/lc-workflow-router/prompts/intake-prompt.md +91 -0
- package/framework/.claude/skills/lc-workflow-router/prompts/router-prompt.md +44 -0
- package/framework/.claude/skills/plan-tdd-development/SKILL.md +31 -0
- package/framework/.claude/skills/plan-tdd-development/prompts/main.md +44 -0
- package/framework/.claude/skills/process-interview/SKILL.md +32 -0
- package/framework/.claude/skills/process-interview/prompts/main.md +47 -0
- package/framework/.claude/skills/propose-with-openspec/SKILL.md +37 -0
- package/framework/.claude/skills/propose-with-openspec/prompts/main.md +53 -0
- package/framework/.claude/skills/split-to-github/SKILL.md +32 -0
- package/framework/.claude/skills/split-to-github/prompts/main.md +42 -0
- package/framework/.claude/skills/sync-to-knowledge/SKILL.md +35 -0
- package/framework/.claude/skills/sync-to-knowledge/prompts/main.md +54 -0
- package/framework/.claude/skills/workflow-continue/SKILL.md +24 -0
- package/framework/.claude/skills/workflow-continue/prompts/main.md +25 -0
- package/framework/.claude/stages/build-codebase-knowledge.yaml +42 -0
- package/framework/.claude/stages/clarify.yaml +59 -0
- package/framework/.claude/stages/explore-opensource.yaml +40 -0
- package/framework/.claude/stages/implement.yaml +60 -0
- package/framework/.claude/stages/ingest.yaml +40 -0
- package/framework/.claude/stages/lc-analyze-frameworks.yaml +37 -0
- package/framework/.claude/stages/lc-collect-intelligence.yaml +37 -0
- package/framework/.claude/stages/lc-competitive-analysis.yaml +37 -0
- package/framework/.claude/stages/lc-first-principles.yaml +37 -0
- package/framework/.claude/stages/lc-market-research.yaml +37 -0
- package/framework/.claude/stages/lc-product-research.yaml +37 -0
- package/framework/.claude/stages/lc-research-report.yaml +38 -0
- package/framework/.claude/stages/lc-research-scope.yaml +38 -0
- package/framework/.claude/stages/lc-stakeholder-research.yaml +37 -0
- package/framework/.claude/stages/lc-storytelling.yaml +37 -0
- package/framework/.claude/stages/product-design-docs.yaml +66 -0
- package/framework/.claude/stages/product-prototype.yaml +53 -0
- package/framework/.claude/stages/propose.yaml +45 -0
- package/framework/.claude/stages/split.yaml +41 -0
- package/framework/.claude/stages/sync-knowledge.yaml +49 -0
- package/framework/.claude/stages/tdd-development-plan.yaml +43 -0
- package/framework/.claude/stages/workflow-router.yaml +45 -0
- package/framework/.claude/templates/solo-workflow-context.yaml +23 -0
- package/framework/.claude/workflows/README.md +93 -0
- package/framework/.claude/workflows/bug-fix.yaml +81 -0
- package/framework/.claude/workflows/design-spike.yaml +55 -0
- package/framework/.claude/workflows/existing-project-iteration.yaml +106 -0
- package/framework/.claude/workflows/interview-to-knowledge.yaml +189 -0
- package/framework/.claude/workflows/oss-first-design.yaml +100 -0
- package/framework/.claude/workflows/pm-research.yaml +115 -0
- package/framework/.claude-plugin/hooks.json +6 -0
- package/framework/.claude-plugin/marketplace.json +32 -0
- package/framework/.claude-plugin/plugin.json +57 -0
- package/framework/.version-bump.json +43 -0
- package/framework/CLAUDE.md +140 -0
- package/framework/CONTRIBUTING.md +219 -0
- package/framework/LICENSE +21 -0
- package/framework/README.en.md +139 -0
- package/framework/README.md +139 -0
- package/framework/RELEASE.md +334 -0
- package/framework/SKILL.md +24 -0
- package/framework/USAGE.md +356 -0
- package/framework/manifest.json +1214 -0
- package/framework/requirements.txt +4 -0
- package/framework/scripts/__init__.py +0 -0
- package/framework/scripts/build_codebase_knowledge.py +76 -0
- package/framework/scripts/bump_version.py +181 -0
- package/framework/scripts/check-dependency-drift.py +69 -0
- package/framework/scripts/check-harness-drift.sh +57 -0
- package/framework/scripts/check-main-merge-hygiene.py +128 -0
- package/framework/scripts/check-skill-dependencies.sh +124 -0
- package/framework/scripts/init-lincoln-branch.sh +300 -0
- package/framework/scripts/init-project.sh +184 -0
- package/framework/scripts/lc-benchmark-cli.py +66 -0
- package/framework/scripts/lincoln-audit.py +288 -0
- package/framework/scripts/lincoln-setup.py +651 -0
- package/framework/scripts/lincoln-status.py +448 -0
- package/framework/scripts/lincoln_benchmark.py +224 -0
- package/framework/scripts/lincoln_benchmark_eval.py +104 -0
- package/framework/scripts/lincoln_benchmark_metrics.py +818 -0
- package/framework/scripts/lincoln_benchmark_report.py +141 -0
- package/framework/scripts/lincoln_command_map.py +289 -0
- package/framework/scripts/lincoln_dependency_manager.py +335 -0
- package/framework/scripts/lincoln_documents.py +175 -0
- package/framework/scripts/lincoln_harness_adapter.py +319 -0
- package/framework/scripts/lincoln_index.py +255 -0
- package/framework/scripts/lincoln_paths.py +198 -0
- package/framework/scripts/lincoln_prd.py +227 -0
- package/framework/scripts/lincoln_role.py +46 -0
- package/framework/scripts/lincoln_scenario.py +59 -0
- package/framework/scripts/lincoln_session_start_metrics.py +71 -0
- package/framework/scripts/lincoln_skill_prompt.py +48 -0
- package/framework/scripts/lincoln_trace.py +335 -0
- package/framework/scripts/lincoln_workflow.py +199 -0
- package/framework/scripts/list-active-lincoln-branches.sh +87 -0
- package/framework/scripts/package-lincoln-plugin.py +340 -0
- package/framework/scripts/run-infrastructure-tests.py +84 -0
- package/framework/scripts/stage_loader.py +1071 -0
- package/framework/scripts/static-check.sh +356 -0
- package/framework/scripts/sync-external-agents.sh +210 -0
- package/framework/scripts/sync-framework-package.mjs +218 -0
- package/framework/scripts/task_tool_guard.py +213 -0
- package/framework/scripts/track-artifacts.py +140 -0
- package/framework/scripts/validate_stage.py +350 -0
- package/{README.md → framework/tools/lincoln/README.md} +4 -4
- package/framework/tools/lincoln/package-lock.json +3349 -0
- package/framework/tools/lincoln/package.json +48 -0
- package/framework/tools/lincoln/src/cli.tsx +60 -0
- package/framework/tools/lincoln/src/components/AudioMeter.tsx +34 -0
- package/framework/tools/lincoln/src/components/CancelledScreen.tsx +11 -0
- package/framework/tools/lincoln/src/components/DevicesMenu.tsx +79 -0
- package/framework/tools/lincoln/src/components/HintBar.tsx +25 -0
- package/framework/tools/lincoln/src/components/LogView.tsx +36 -0
- package/framework/tools/lincoln/src/components/ModelMenu.tsx +122 -0
- package/framework/tools/lincoln/src/components/ReadyScreen.tsx +54 -0
- package/framework/tools/lincoln/src/components/RecordingApp.tsx +211 -0
- package/framework/tools/lincoln/src/components/RecordingScreen.tsx +41 -0
- package/framework/tools/lincoln/src/components/SelectMenu.tsx +53 -0
- package/framework/tools/lincoln/src/components/StatusPanel.tsx +52 -0
- package/framework/tools/lincoln/src/components/StopConfirmation.tsx +32 -0
- package/framework/tools/lincoln/src/config/args.ts +35 -0
- package/framework/tools/lincoln/src/config/loadConfig.ts +81 -0
- package/framework/tools/lincoln/src/config/resolveConfig.ts +85 -0
- package/framework/tools/lincoln/src/devices/listDevices.ts +11 -0
- package/framework/tools/lincoln/src/devices/parseDevices.ts +25 -0
- package/framework/tools/lincoln/src/hooks/useKeyHandler.ts +63 -0
- package/framework/tools/lincoln/src/main.tsx +4 -0
- package/framework/tools/lincoln/src/models/models.ts +34 -0
- package/framework/tools/lincoln/src/models/runWarmup.ts +46 -0
- package/framework/tools/lincoln/src/models/warmupProgress.ts +30 -0
- package/framework/tools/lincoln/src/recording/spawnRecorder.ts +127 -0
- package/framework/tools/lincoln/src/recording/useRecorder.ts +146 -0
- package/framework/tools/lincoln/src/utils/amplitudeToMeter.ts +4 -0
- package/framework/tools/lincoln/src/utils/formatDuration.ts +6 -0
- package/framework/tools/lincoln/src/utils/sessionId.ts +25 -0
- package/framework/tools/lincoln/tests/cli.test.tsx +52 -0
- package/framework/tools/lincoln/tests/components/AudioMeter.test.tsx +31 -0
- package/framework/tools/lincoln/tests/components/CancelledScreen.test.tsx +12 -0
- package/framework/tools/lincoln/tests/components/ReadyScreen.test.tsx +68 -0
- package/framework/tools/lincoln/tests/components/RecordingApp.menus.test.tsx +158 -0
- package/framework/tools/lincoln/tests/components/RecordingApp.test.tsx +248 -0
- package/framework/tools/lincoln/tests/components/RecordingScreen.test.tsx +37 -0
- package/framework/tools/lincoln/tests/components/SelectMenu.test.tsx +59 -0
- package/framework/tools/lincoln/tests/components/StopConfirmation.test.tsx +38 -0
- package/framework/tools/lincoln/tests/config/args.test.ts +49 -0
- package/framework/tools/lincoln/tests/config/loadConfig.test.ts +59 -0
- package/framework/tools/lincoln/tests/config/resolveConfig.test.ts +72 -0
- package/framework/tools/lincoln/tests/devices/parseDevices.test.ts +21 -0
- package/framework/tools/lincoln/tests/hooks/useKeyHandler.enabled.test.tsx +44 -0
- package/framework/tools/lincoln/tests/hooks/useKeyHandler.test.tsx +130 -0
- package/framework/tools/lincoln/tests/models/models.test.ts +27 -0
- package/framework/tools/lincoln/tests/models/warmupProgress.test.ts +25 -0
- package/framework/tools/lincoln/tests/recording/spawnRecorder.test.ts +85 -0
- package/framework/tools/lincoln/tests/recording/useRecorder.test.tsx +172 -0
- package/framework/tools/lincoln/tests/utils/amplitudeToMeter.test.ts +22 -0
- package/framework/tools/lincoln/tests/utils/formatDuration.test.ts +11 -0
- package/framework/tools/lincoln/tests/utils/sessionId.test.ts +30 -0
- package/framework/tools/lincoln/tsconfig.json +22 -0
- package/framework/tools/lincoln/tsconfig.tests.json +10 -0
- package/framework/tools/lincoln/tsup.config.ts +17 -0
- package/framework/tools/lincoln/vitest.config.ts +20 -0
- package/framework/tools/lincoln-installer/package-lock.json +3611 -0
- package/framework/tools/lincoln-installer/package.json +53 -0
- package/framework/tools/lincoln-installer/src/cli.tsx +63 -0
- package/framework/tools/lincoln-installer/src/components/HarnessSelectScreen.tsx +43 -0
- package/framework/tools/lincoln-installer/src/components/InstallerApp.tsx +98 -0
- package/framework/tools/lincoln-installer/src/components/OptionsScreen.tsx +56 -0
- package/framework/tools/lincoln-installer/src/components/ProgressScreen.tsx +25 -0
- package/framework/tools/lincoln-installer/src/components/ResultScreen.tsx +40 -0
- package/framework/tools/lincoln-installer/src/components/SelectMenu.tsx +73 -0
- package/framework/tools/lincoln-installer/src/components/SummaryScreen.tsx +49 -0
- package/framework/tools/lincoln-installer/src/components/UpdateApp.tsx +135 -0
- package/framework/tools/lincoln-installer/src/components/WelcomeScreen.tsx +22 -0
- package/framework/tools/lincoln-installer/src/config/args.ts +76 -0
- package/framework/tools/lincoln-installer/src/config/resolveConfig.ts +33 -0
- package/framework/tools/lincoln-installer/src/config/updateArgs.ts +67 -0
- package/framework/tools/lincoln-installer/src/hooks/useSetup.ts +149 -0
- package/framework/tools/lincoln-installer/src/main.tsx +11 -0
- package/framework/tools/lincoln-installer/src/types.ts +19 -0
- package/framework/tools/lincoln-installer/src/update-main.tsx +11 -0
- package/framework/tools/lincoln-installer/src/update.tsx +90 -0
- package/framework/tools/lincoln-installer/src/utils/detectHarness.ts +51 -0
- package/framework/tools/lincoln-installer/src/utils/runSetup.ts +62 -0
- package/framework/tools/lincoln-installer/src/utils/updateRelease.ts +454 -0
- package/framework/tools/lincoln-installer/tests/components/InstallerApp.test.tsx +88 -0
- package/framework/tools/lincoln-installer/tests/components/SelectMenu.test.tsx +26 -0
- package/framework/tools/lincoln-installer/tests/components/UpdateApp.test.tsx +170 -0
- package/framework/tools/lincoln-installer/tests/components/screens.test.tsx +310 -0
- package/framework/tools/lincoln-installer/tests/config/args.test.ts +66 -0
- package/framework/tools/lincoln-installer/tests/config/resolveConfig.test.ts +75 -0
- package/framework/tools/lincoln-installer/tests/config/updateArgs.test.ts +52 -0
- package/framework/tools/lincoln-installer/tests/hooks/useSetup.test.tsx +213 -0
- package/framework/tools/lincoln-installer/tests/update.test.tsx +108 -0
- package/framework/tools/lincoln-installer/tests/utils/detectHarness.test.ts +40 -0
- package/framework/tools/lincoln-installer/tests/utils/runSetup.test.ts +45 -0
- package/framework/tools/lincoln-installer/tests/utils/updateRelease.test.ts +559 -0
- package/framework/tools/lincoln-installer/tsconfig.json +22 -0
- package/framework/tools/lincoln-installer/tsconfig.tests.json +8 -0
- package/framework/tools/lincoln-installer/tsup.config.ts +17 -0
- package/framework/tools/lincoln-installer/vitest.config.ts +20 -0
- package/framework/tools/lincoln-record/Cargo.lock +1896 -0
- package/framework/tools/lincoln-record/Cargo.toml +24 -0
- package/framework/tools/lincoln-record/diarize/diarize.py +117 -0
- package/framework/tools/lincoln-record/diarize/merge.py +55 -0
- package/framework/tools/lincoln-record/src/audio/capture/microphone.rs +194 -0
- package/framework/tools/lincoln-record/src/audio/capture/mod.rs +1 -0
- package/framework/tools/lincoln-record/src/audio/devices.rs +20 -0
- package/framework/tools/lincoln-record/src/audio/mod.rs +5 -0
- package/framework/tools/lincoln-record/src/audio/resampler.rs +44 -0
- package/framework/tools/lincoln-record/src/audio/saver.rs +87 -0
- package/framework/tools/lincoln-record/src/audio/source.rs +104 -0
- package/framework/tools/lincoln-record/src/cli.rs +96 -0
- package/framework/tools/lincoln-record/src/config.rs +135 -0
- package/framework/tools/lincoln-record/src/lib.rs +6 -0
- package/framework/tools/lincoln-record/src/main.rs +242 -0
- package/framework/tools/lincoln-record/src/model/mod.rs +248 -0
- package/framework/tools/lincoln-record/src/output/metadata.rs +30 -0
- package/framework/tools/lincoln-record/src/output/mod.rs +2 -0
- package/framework/tools/lincoln-record/src/output/transcript.rs +42 -0
- package/framework/tools/lincoln-record/src/transcription/diarization.rs +64 -0
- package/framework/tools/lincoln-record/src/transcription/mod.rs +3 -0
- package/framework/tools/lincoln-record/src/transcription/provider.rs +43 -0
- package/framework/tools/lincoln-record/src/transcription/whisper.rs +176 -0
- package/framework/tools/lincoln-record/tests/cli_args.rs +103 -0
- package/framework/tools/lincoln-record/tests/config_test.rs +70 -0
- package/framework/tools/lincoln-record/tests/devices_test.rs +23 -0
- package/framework/tools/lincoln-record/tests/diarization_tests.rs +101 -0
- package/framework/tools/lincoln-record/tests/integration_tests.rs +44 -0
- package/framework/tools/lincoln-record/tests/model_test.rs +68 -0
- package/framework/tools/lincoln-record/tests/transcription_tests.rs +73 -0
- package/package.json +19 -13
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Sync Lincoln framework runtime payload into packages/lincoln/framework/.
|
|
4
|
+
*
|
|
5
|
+
* This script mirrors the allowlist used by scripts/package-lincoln-plugin.py
|
|
6
|
+
* so the npm package can bundle the same files that the release tarball uses.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import crypto from 'node:crypto'
|
|
10
|
+
import fs from 'node:fs/promises'
|
|
11
|
+
import path from 'node:path'
|
|
12
|
+
import { fileURLToPath } from 'node:url'
|
|
13
|
+
|
|
14
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
15
|
+
const ROOT = path.resolve(path.dirname(__filename), '..')
|
|
16
|
+
const PAYLOAD_DIR = path.join(ROOT, 'packages', 'lincoln', 'framework')
|
|
17
|
+
const VERSION_FILE = path.join(ROOT, '.version-bump.json')
|
|
18
|
+
|
|
19
|
+
const ALLOWLIST_DIRS = ['.claude', '.claude-plugin', 'scripts', 'tools']
|
|
20
|
+
const ALLOWLIST_FILES = [
|
|
21
|
+
'README.md',
|
|
22
|
+
'README.en.md',
|
|
23
|
+
'USAGE.md',
|
|
24
|
+
'CONTRIBUTING.md',
|
|
25
|
+
'CLAUDE.md',
|
|
26
|
+
'LICENSE',
|
|
27
|
+
'RELEASE.md',
|
|
28
|
+
'.version-bump.json',
|
|
29
|
+
'requirements.txt',
|
|
30
|
+
'SKILL.md'
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
const DENYLIST_NAMES = new Set([
|
|
34
|
+
'.git',
|
|
35
|
+
'.context',
|
|
36
|
+
'.venv',
|
|
37
|
+
'venv',
|
|
38
|
+
'oss',
|
|
39
|
+
'.pytest_cache',
|
|
40
|
+
'__pycache__',
|
|
41
|
+
'.DS_Store',
|
|
42
|
+
'node_modules'
|
|
43
|
+
])
|
|
44
|
+
const DENYLIST_PREFIXES = ['issue-']
|
|
45
|
+
const SKIP_PATHS = new Set([
|
|
46
|
+
'.claude/templates/issue-package',
|
|
47
|
+
'tools/lincoln-installer/dist',
|
|
48
|
+
'tools/lincoln-installer/node_modules',
|
|
49
|
+
'tools/lincoln-installer/coverage'
|
|
50
|
+
])
|
|
51
|
+
|
|
52
|
+
async function loadVersion() {
|
|
53
|
+
const raw = await fs.readFile(VERSION_FILE, 'utf8')
|
|
54
|
+
const data = JSON.parse(raw)
|
|
55
|
+
if (typeof data.version !== 'string') {
|
|
56
|
+
throw new Error('.version-bump.json missing version string')
|
|
57
|
+
}
|
|
58
|
+
return data.version
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function isDenied(relativePath) {
|
|
62
|
+
const parts = relativePath.split(path.sep)
|
|
63
|
+
for (const part of parts) {
|
|
64
|
+
if (DENYLIST_NAMES.has(part)) return true
|
|
65
|
+
for (const prefix of DENYLIST_PREFIXES) {
|
|
66
|
+
if (part.startsWith(prefix)) return true
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
for (const skip of SKIP_PATHS) {
|
|
70
|
+
if (relativePath === skip || relativePath.startsWith(skip + path.sep)) {
|
|
71
|
+
return true
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return false
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function* walk(dir, baseDir) {
|
|
78
|
+
const entries = await fs.readdir(dir, { withFileTypes: true })
|
|
79
|
+
for (const entry of entries) {
|
|
80
|
+
const fullPath = path.join(dir, entry.name)
|
|
81
|
+
const relativePath = path.relative(baseDir, fullPath)
|
|
82
|
+
if (isDenied(relativePath)) continue
|
|
83
|
+
if (entry.isDirectory()) {
|
|
84
|
+
yield* walk(fullPath, baseDir)
|
|
85
|
+
} else if (entry.isFile()) {
|
|
86
|
+
yield { fullPath, relativePath }
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function sha256(filePath) {
|
|
92
|
+
const data = await fs.readFile(filePath)
|
|
93
|
+
return crypto.createHash('sha256').update(data).digest('hex')
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function syncPayload(version) {
|
|
97
|
+
await fs.rm(PAYLOAD_DIR, { recursive: true, force: true })
|
|
98
|
+
await fs.mkdir(PAYLOAD_DIR, { recursive: true })
|
|
99
|
+
|
|
100
|
+
const copied = []
|
|
101
|
+
|
|
102
|
+
for (const dir of ALLOWLIST_DIRS) {
|
|
103
|
+
const src = path.join(ROOT, dir)
|
|
104
|
+
try {
|
|
105
|
+
await fs.access(src)
|
|
106
|
+
} catch {
|
|
107
|
+
continue
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
for await (const { fullPath, relativePath } of walk(src, ROOT)) {
|
|
111
|
+
const dest = path.join(PAYLOAD_DIR, relativePath)
|
|
112
|
+
await fs.mkdir(path.dirname(dest), { recursive: true })
|
|
113
|
+
await fs.copyFile(fullPath, dest)
|
|
114
|
+
copied.push(relativePath)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
for (const file of ALLOWLIST_FILES) {
|
|
119
|
+
const src = path.join(ROOT, file)
|
|
120
|
+
try {
|
|
121
|
+
await fs.access(src)
|
|
122
|
+
} catch {
|
|
123
|
+
continue
|
|
124
|
+
}
|
|
125
|
+
const dest = path.join(PAYLOAD_DIR, file)
|
|
126
|
+
await fs.mkdir(path.dirname(dest), { recursive: true })
|
|
127
|
+
await fs.copyFile(src, dest)
|
|
128
|
+
copied.push(file)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
copied.sort()
|
|
132
|
+
|
|
133
|
+
const manifestFiles = []
|
|
134
|
+
for (const relativePath of copied) {
|
|
135
|
+
const filePath = path.join(PAYLOAD_DIR, relativePath)
|
|
136
|
+
manifestFiles.push({
|
|
137
|
+
path: relativePath,
|
|
138
|
+
sha256: await sha256(filePath)
|
|
139
|
+
})
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const manifest = {
|
|
143
|
+
version,
|
|
144
|
+
generatedAt: new Date().toISOString(),
|
|
145
|
+
files: manifestFiles
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
await fs.writeFile(
|
|
149
|
+
path.join(PAYLOAD_DIR, 'manifest.json'),
|
|
150
|
+
JSON.stringify(manifest, null, 2) + '\n'
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
return copied
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async function checkPayload(version) {
|
|
157
|
+
try {
|
|
158
|
+
await fs.access(PAYLOAD_DIR)
|
|
159
|
+
} catch {
|
|
160
|
+
return { ok: false, reason: 'framework payload directory missing' }
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const manifestPath = path.join(PAYLOAD_DIR, 'manifest.json')
|
|
164
|
+
let manifest
|
|
165
|
+
try {
|
|
166
|
+
const raw = await fs.readFile(manifestPath, 'utf8')
|
|
167
|
+
manifest = JSON.parse(raw)
|
|
168
|
+
} catch {
|
|
169
|
+
return { ok: false, reason: 'manifest.json missing or unreadable' }
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (manifest.version !== version) {
|
|
173
|
+
return { ok: false, reason: `version mismatch: ${manifest.version} != ${version}` }
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
for (const entry of manifest.files) {
|
|
177
|
+
const filePath = path.join(PAYLOAD_DIR, entry.path)
|
|
178
|
+
try {
|
|
179
|
+
const actual = await sha256(filePath)
|
|
180
|
+
if (actual !== entry.sha256) {
|
|
181
|
+
return { ok: false, reason: `checksum mismatch: ${entry.path}` }
|
|
182
|
+
}
|
|
183
|
+
} catch {
|
|
184
|
+
return { ok: false, reason: `missing file: ${entry.path}` }
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return { ok: true }
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async function main() {
|
|
192
|
+
const args = process.argv.slice(2)
|
|
193
|
+
const version = await loadVersion()
|
|
194
|
+
|
|
195
|
+
if (args.includes('--dump-allowlist')) {
|
|
196
|
+
console.log(JSON.stringify({ dirs: ALLOWLIST_DIRS, files: ALLOWLIST_FILES }, null, 2))
|
|
197
|
+
return 0
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (args.includes('--check')) {
|
|
201
|
+
const result = await checkPayload(version)
|
|
202
|
+
if (result.ok) {
|
|
203
|
+
console.log(`framework payload up to date (${version})`)
|
|
204
|
+
return 0
|
|
205
|
+
}
|
|
206
|
+
console.error(`framework payload out of sync: ${result.reason}`)
|
|
207
|
+
return 1
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const copied = await syncPayload(version)
|
|
211
|
+
console.log(`synced ${copied.length} files to ${PAYLOAD_DIR} (${version})`)
|
|
212
|
+
return 0
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
main().then((code) => process.exit(code)).catch((err) => {
|
|
216
|
+
console.error(err)
|
|
217
|
+
process.exit(1)
|
|
218
|
+
})
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Task-tool guard for the Lincoln workflow.
|
|
4
|
+
|
|
5
|
+
Prevents the agent from using TaskCreate/TaskUpdate as placeholders for
|
|
6
|
+
"I will send a user message soon" in dialogue stages, and limits consecutive
|
|
7
|
+
task-tool calls everywhere else so a runaway loop cannot accumulate hundreds
|
|
8
|
+
of task artifacts without a non-task action in between.
|
|
9
|
+
|
|
10
|
+
Intended to be invoked from .claude/hooks/pre-tool-use.sh for every tool call.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import argparse
|
|
16
|
+
import json
|
|
17
|
+
import os
|
|
18
|
+
import sys
|
|
19
|
+
import tempfile
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Any
|
|
22
|
+
|
|
23
|
+
import yaml
|
|
24
|
+
|
|
25
|
+
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
|
26
|
+
try:
|
|
27
|
+
from scripts.lincoln_paths import resolve_state_path
|
|
28
|
+
except Exception: # pragma: no cover - standalone fallback
|
|
29
|
+
resolve_state_path = None # type: ignore[assignment]
|
|
30
|
+
|
|
31
|
+
# Stages where the agent is expected to talk to the PM, not to manage a task
|
|
32
|
+
# list. We default this from the workflow YAML (human_gate: true), but allow
|
|
33
|
+
# an override for tests.
|
|
34
|
+
DIALOGUE_STAGE_FALLBACK: frozenset[str] = frozenset(
|
|
35
|
+
{"clarify", "product-design-docs", "product-prototype", "implement"}
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
TASK_TOOLS: frozenset[str] = frozenset({"TaskCreate", "TaskUpdate"})
|
|
39
|
+
|
|
40
|
+
DEFAULT_MAX_CONSECUTIVE = 3
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def load_yaml(path: Path) -> Any:
|
|
44
|
+
if not path.exists():
|
|
45
|
+
raise FileNotFoundError(path)
|
|
46
|
+
return yaml.safe_load(path.read_text(encoding="utf-8"))
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def load_state(state_file: Path) -> dict[str, Any] | None:
|
|
50
|
+
if not state_file.exists():
|
|
51
|
+
return None
|
|
52
|
+
data = load_yaml(state_file)
|
|
53
|
+
if not isinstance(data, dict):
|
|
54
|
+
return None
|
|
55
|
+
return data
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def resolve_project_root(state_file: Path) -> Path | None:
|
|
59
|
+
"""Derive project root from <root>/.claude/workflow-state.yaml."""
|
|
60
|
+
resolved = state_file.resolve()
|
|
61
|
+
if len(resolved.parents) < 2:
|
|
62
|
+
return None
|
|
63
|
+
return resolved.parents[1]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def dialogue_stages(workflow_file: Path | None, override: set[str] | None) -> set[str]:
|
|
67
|
+
if override is not None:
|
|
68
|
+
return override
|
|
69
|
+
if workflow_file is None or not workflow_file.exists():
|
|
70
|
+
return set(DIALOGUE_STAGE_FALLBACK)
|
|
71
|
+
try:
|
|
72
|
+
data = load_yaml(workflow_file)
|
|
73
|
+
workflow = data.get("workflow", data) if isinstance(data, dict) else {}
|
|
74
|
+
return {
|
|
75
|
+
step["id"]
|
|
76
|
+
for step in workflow.get("steps", [])
|
|
77
|
+
if isinstance(step, dict) and step.get("human_gate")
|
|
78
|
+
}
|
|
79
|
+
except (yaml.YAMLError, AttributeError, KeyError, TypeError):
|
|
80
|
+
return set(DIALOGUE_STAGE_FALLBACK)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def counter_file_path(state_file: Path) -> Path:
|
|
84
|
+
root = resolve_project_root(state_file)
|
|
85
|
+
if root is None:
|
|
86
|
+
# Unusual path depth; keep the counter next to the state file.
|
|
87
|
+
return state_file.resolve().parent / ".context" / "task-tool-burst.json"
|
|
88
|
+
return root / ".context" / "task-tool-burst.json"
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def load_counter(counter_file: Path) -> dict[str, Any]:
|
|
92
|
+
if not counter_file.exists():
|
|
93
|
+
return {"count": 0, "last_stage": None, "last_tool": None}
|
|
94
|
+
try:
|
|
95
|
+
data = json.loads(counter_file.read_text(encoding="utf-8"))
|
|
96
|
+
if isinstance(data, dict):
|
|
97
|
+
return data
|
|
98
|
+
except json.JSONDecodeError:
|
|
99
|
+
pass
|
|
100
|
+
return {"count": 0, "last_stage": None, "last_tool": None}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def save_counter(counter_file: Path, data: dict[str, Any]) -> None:
|
|
104
|
+
counter_file.parent.mkdir(parents=True, exist_ok=True)
|
|
105
|
+
tmp_fd, tmp_path = tempfile.mkstemp(dir=counter_file.parent, suffix=".json.tmp")
|
|
106
|
+
try:
|
|
107
|
+
with os.fdopen(tmp_fd, "w", encoding="utf-8") as f:
|
|
108
|
+
json.dump(data, f, ensure_ascii=False, indent=2)
|
|
109
|
+
os.replace(tmp_path, counter_file)
|
|
110
|
+
except Exception:
|
|
111
|
+
try:
|
|
112
|
+
os.close(tmp_fd)
|
|
113
|
+
except OSError:
|
|
114
|
+
pass
|
|
115
|
+
if os.path.exists(tmp_path):
|
|
116
|
+
os.unlink(tmp_path)
|
|
117
|
+
raise
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def block(message: str) -> int:
|
|
121
|
+
print(f"BLOCKED: {message}", file=sys.stderr)
|
|
122
|
+
return 1
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def allow() -> int:
|
|
126
|
+
return 0
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def main(argv: list[str] | None = None) -> int:
|
|
130
|
+
parser = argparse.ArgumentParser(description="Lincoln task-tool guard")
|
|
131
|
+
parser.add_argument("--state-file", type=Path, default=None)
|
|
132
|
+
parser.add_argument("--tool-name", required=True)
|
|
133
|
+
parser.add_argument("--tool-args", default="{}")
|
|
134
|
+
parser.add_argument(
|
|
135
|
+
"--dialogue-stages",
|
|
136
|
+
help="Comma-separated list of stage IDs where task tools are forbidden",
|
|
137
|
+
)
|
|
138
|
+
parser.add_argument(
|
|
139
|
+
"--max-consecutive",
|
|
140
|
+
type=int,
|
|
141
|
+
default=DEFAULT_MAX_CONSECUTIVE,
|
|
142
|
+
help="Maximum allowed consecutive TaskCreate/TaskUpdate calls",
|
|
143
|
+
)
|
|
144
|
+
args = parser.parse_args(argv)
|
|
145
|
+
|
|
146
|
+
state_file = args.state_file
|
|
147
|
+
if state_file is None:
|
|
148
|
+
env_path = os.environ.get("LINCOLN_STATE_FILE")
|
|
149
|
+
if env_path:
|
|
150
|
+
state_file = Path(env_path)
|
|
151
|
+
elif resolve_state_path is not None:
|
|
152
|
+
state_file = resolve_state_path(None, Path(__file__).resolve().parents[1])
|
|
153
|
+
else:
|
|
154
|
+
state_file = Path.cwd() / ".claude" / "workflow-state.yaml"
|
|
155
|
+
|
|
156
|
+
state = load_state(state_file)
|
|
157
|
+
if state is None:
|
|
158
|
+
# Not a Lincoln project or not initialized yet; stay out of the way.
|
|
159
|
+
return allow()
|
|
160
|
+
|
|
161
|
+
current_stage = state.get("current_run", {}).get("current_stage") or "not_started"
|
|
162
|
+
|
|
163
|
+
# Reset the burst counter whenever a non-task tool is used.
|
|
164
|
+
if args.tool_name not in TASK_TOOLS:
|
|
165
|
+
counter_file = counter_file_path(state_file)
|
|
166
|
+
save_counter(
|
|
167
|
+
counter_file,
|
|
168
|
+
{"count": 0, "last_stage": current_stage, "last_tool": args.tool_name},
|
|
169
|
+
)
|
|
170
|
+
return allow()
|
|
171
|
+
|
|
172
|
+
# We are handling a task tool from here on.
|
|
173
|
+
override_stages = None
|
|
174
|
+
if args.dialogue_stages is not None:
|
|
175
|
+
override_stages = {s.strip() for s in args.dialogue_stages.split(",") if s.strip()}
|
|
176
|
+
|
|
177
|
+
root = resolve_project_root(state_file)
|
|
178
|
+
workflow_file = None
|
|
179
|
+
if root is not None:
|
|
180
|
+
workflow_file = root / ".claude" / "workflows" / "interview-to-knowledge.yaml"
|
|
181
|
+
|
|
182
|
+
dialogue = dialogue_stages(workflow_file, override_stages)
|
|
183
|
+
|
|
184
|
+
if current_stage in dialogue:
|
|
185
|
+
return block(
|
|
186
|
+
f"TaskCreate/TaskUpdate are not allowed in the dialogue stage '{current_stage}'. "
|
|
187
|
+
"Send your message to the user directly instead of using task tools as placeholders."
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
counter_file = counter_file_path(state_file)
|
|
191
|
+
counter = load_counter(counter_file)
|
|
192
|
+
|
|
193
|
+
if counter.get("last_stage") != current_stage or counter.get("last_tool") not in TASK_TOOLS:
|
|
194
|
+
counter = {"count": 1, "last_stage": current_stage, "last_tool": args.tool_name}
|
|
195
|
+
else:
|
|
196
|
+
counter = {
|
|
197
|
+
**counter,
|
|
198
|
+
"count": counter.get("count", 0) + 1,
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
save_counter(counter_file, counter)
|
|
202
|
+
|
|
203
|
+
if counter["count"] > args.max_consecutive:
|
|
204
|
+
return block(
|
|
205
|
+
f"Too many consecutive task-tool calls ({counter['count']}) in stage '{current_stage}'. "
|
|
206
|
+
"Perform a non-task action (e.g. Read or send a user message) before creating/updating more tasks."
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
return allow()
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
if __name__ == "__main__":
|
|
213
|
+
sys.exit(main())
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Track artifacts produced by side-effect tools and update workflow-state.yaml.
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
python scripts/track-artifacts.py --state-file .claude/workflow-state.yaml \
|
|
7
|
+
--tool Write --args '{"file_path": "designs/foo/bar.md"}'
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
import json
|
|
14
|
+
import re
|
|
15
|
+
import sys
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
import yaml
|
|
19
|
+
|
|
20
|
+
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
|
21
|
+
from scripts.lincoln_paths import get_process_slug
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# Paths that are considered workflow artifacts (relative to project root).
|
|
25
|
+
ARTIFACT_PREFIXES = (
|
|
26
|
+
"interviews/",
|
|
27
|
+
"requirements/",
|
|
28
|
+
"designs/",
|
|
29
|
+
"openspec/",
|
|
30
|
+
"knowledge/",
|
|
31
|
+
".github/linked-issues.yaml",
|
|
32
|
+
".github/lincoln-sync-queue/",
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def load_state(path: Path) -> dict:
|
|
37
|
+
return yaml.safe_load(path.read_text(encoding="utf-8"))
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def save_state(path: Path, state: dict) -> None:
|
|
41
|
+
path.write_text(yaml.dump(state, allow_unicode=True, sort_keys=False), encoding="utf-8")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def extract_paths(tool: str, args: str, project_root: Path) -> list[str]:
|
|
45
|
+
"""Extract relative file paths from tool arguments (best-effort)."""
|
|
46
|
+
paths: list[str] = []
|
|
47
|
+
|
|
48
|
+
# For Write/Edit the argument is JSON with file_path
|
|
49
|
+
if tool in ("Write", "Edit"):
|
|
50
|
+
try:
|
|
51
|
+
data = json.loads(args)
|
|
52
|
+
file_path = data.get("file_path") or data.get("path")
|
|
53
|
+
if file_path:
|
|
54
|
+
paths.append(str(file_path))
|
|
55
|
+
except json.JSONDecodeError:
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
# For Bash, scan for common output paths in the command string
|
|
59
|
+
if tool == "Bash":
|
|
60
|
+
try:
|
|
61
|
+
data = json.loads(args)
|
|
62
|
+
command = data.get("command", "")
|
|
63
|
+
except json.JSONDecodeError:
|
|
64
|
+
command = args
|
|
65
|
+
# Match paths that look like output files
|
|
66
|
+
for match in re.finditer(r"(?:\s|=|>\s?)([\w\-./]+\.(?:md|yaml|yml|json|pen))", command):
|
|
67
|
+
paths.append(match.group(1))
|
|
68
|
+
|
|
69
|
+
# For Pencil MCP tools, we can't easily know exported files; skip.
|
|
70
|
+
|
|
71
|
+
# Normalize and filter to project-relative artifact paths.
|
|
72
|
+
result: list[str] = []
|
|
73
|
+
for p in paths:
|
|
74
|
+
raw = Path(p.strip())
|
|
75
|
+
if raw.is_absolute():
|
|
76
|
+
try:
|
|
77
|
+
p = str(raw.resolve().relative_to(project_root.resolve()))
|
|
78
|
+
except ValueError:
|
|
79
|
+
continue
|
|
80
|
+
else:
|
|
81
|
+
p = str(raw).lstrip("/")
|
|
82
|
+
if any(p.startswith(prefix) for prefix in ARTIFACT_PREFIXES) or "/recordings/" in p or "/requirements/" in p or "/designs/" in p or "/interviews/" in p or "/openspec/" in p:
|
|
83
|
+
result.append(p)
|
|
84
|
+
return result
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def main() -> int:
|
|
88
|
+
parser = argparse.ArgumentParser()
|
|
89
|
+
parser.add_argument("--state-file", type=Path, required=True)
|
|
90
|
+
parser.add_argument("--tool", required=True)
|
|
91
|
+
parser.add_argument("--args", default="{}")
|
|
92
|
+
parser.add_argument("--project-root", type=Path, default=Path.cwd())
|
|
93
|
+
args = parser.parse_args()
|
|
94
|
+
|
|
95
|
+
if not args.state_file.exists():
|
|
96
|
+
return 0
|
|
97
|
+
|
|
98
|
+
state = load_state(args.state_file)
|
|
99
|
+
current_stage = state.get("current_run", {}).get("current_stage")
|
|
100
|
+
if not current_stage:
|
|
101
|
+
return 0
|
|
102
|
+
|
|
103
|
+
new_paths = extract_paths(args.tool, args.args, args.project_root)
|
|
104
|
+
if not new_paths:
|
|
105
|
+
return 0
|
|
106
|
+
|
|
107
|
+
if "nodes" in state:
|
|
108
|
+
matching = [n for n in state.setdefault("nodes", []) if n.get("stage_id") == current_stage]
|
|
109
|
+
if matching:
|
|
110
|
+
node = matching[-1]
|
|
111
|
+
else:
|
|
112
|
+
node = {
|
|
113
|
+
"stage_id": current_stage,
|
|
114
|
+
"node_id": f"{current_stage}-artifact-tracking",
|
|
115
|
+
"status": state.get("current_run", {}).get("status", "in_progress"),
|
|
116
|
+
"started_at": state.get("current_run", {}).get("last_updated_at"),
|
|
117
|
+
"completed_at": None,
|
|
118
|
+
"gate_passed": False,
|
|
119
|
+
"approved_by": None,
|
|
120
|
+
"artifacts": [],
|
|
121
|
+
"handoff_file": None,
|
|
122
|
+
}
|
|
123
|
+
state["nodes"].append(node)
|
|
124
|
+
existing = set(node.get("artifacts", []))
|
|
125
|
+
node["artifacts"] = sorted(existing | set(new_paths))
|
|
126
|
+
else:
|
|
127
|
+
stage_state = state.setdefault("stages", {}).setdefault(current_stage, {})
|
|
128
|
+
existing = set(stage_state.get("artifacts_produced", []))
|
|
129
|
+
stage_state["artifacts_produced"] = sorted(existing | set(new_paths))
|
|
130
|
+
|
|
131
|
+
state.setdefault("current_run", {}).setdefault("variables", {}).setdefault(
|
|
132
|
+
"process_slug",
|
|
133
|
+
get_process_slug(state, args.state_file),
|
|
134
|
+
)
|
|
135
|
+
save_state(args.state_file, state)
|
|
136
|
+
return 0
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
if __name__ == "__main__":
|
|
140
|
+
sys.exit(main())
|