@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.
Files changed (305) hide show
  1. package/dist/main.d.ts +3 -0
  2. package/dist/main.js +1231 -906
  3. package/dist/main.js.map +1 -1
  4. package/dist/postinstall.d.ts +3 -0
  5. package/dist/postinstall.js +18 -0
  6. package/dist/postinstall.js.map +1 -0
  7. package/framework/.claude/agents/_contract.md +79 -0
  8. package/framework/.claude/agents/architect.md +31 -0
  9. package/framework/.claude/agents/default.md +151 -0
  10. package/framework/.claude/agents/designer.md +33 -0
  11. package/framework/.claude/agents/engineer.md +42 -0
  12. package/framework/.claude/agents/external/NOTICES.md +12 -0
  13. package/framework/.claude/agents/external/everything-claude-code.manifest.yaml +23 -0
  14. package/framework/.claude/agents/external/oh-my-claudecode.manifest.yaml +26 -0
  15. package/framework/.claude/agents/external/wshobson-agents.manifest.yaml +23 -0
  16. package/framework/.claude/agents/frontend-engineer.md +32 -0
  17. package/framework/.claude/agents/knowledge-curator.md +28 -0
  18. package/framework/.claude/agents/pm.md +38 -0
  19. package/framework/.claude/agents/qa.md +28 -0
  20. package/framework/.claude/agents/release-coordinator.md +25 -0
  21. package/framework/.claude/agents/researcher.md +41 -0
  22. package/framework/.claude/harnesses/claude-code.yaml +15 -0
  23. package/framework/.claude/harnesses/codex.yaml +40 -0
  24. package/framework/.claude/harnesses/command-map.yaml +255 -0
  25. package/framework/.claude/harnesses/opencode.yaml +32 -0
  26. package/framework/.claude/harnesses/scenarios.yaml +45 -0
  27. package/framework/.claude/hooks/on-pr-event.sh +69 -0
  28. package/framework/.claude/hooks/on-session-start.sh +427 -0
  29. package/framework/.claude/hooks/on-stop.sh +104 -0
  30. package/framework/.claude/hooks/post-tool-use.sh +161 -0
  31. package/framework/.claude/hooks/pre-tool-use.sh +246 -0
  32. package/framework/.claude/schemas/agent-definition.schema.json +35 -0
  33. package/framework/.claude/schemas/benchmark-report.schema.json +148 -0
  34. package/framework/.claude/schemas/pm-to-ux-handoff.schema.yaml +128 -0
  35. package/framework/.claude/schemas/stage-definition.schema.json +233 -0
  36. package/framework/.claude/schemas/workflow-stage.schema.json +86 -0
  37. package/framework/.claude/schemas/workflow-template.schema.json +88 -0
  38. package/framework/.claude/settings.json +14 -0
  39. package/framework/.claude/skills/build-product-prototype/SKILL.md +35 -0
  40. package/framework/.claude/skills/build-product-prototype/prompts/main.md +78 -0
  41. package/framework/.claude/skills/clarify-requirements/SKILL.md +32 -0
  42. package/framework/.claude/skills/clarify-requirements/prompts/main.md +81 -0
  43. package/framework/.claude/skills/dependencies.yaml +139 -0
  44. package/framework/.claude/skills/draft-product-design/SKILL.md +40 -0
  45. package/framework/.claude/skills/draft-product-design/prompts/main.md +47 -0
  46. package/framework/.claude/skills/lc-analyze-frameworks/SKILL.md +43 -0
  47. package/framework/.claude/skills/lc-analyze-frameworks/prompts/main.md +95 -0
  48. package/framework/.claude/skills/lc-benchmark/SKILL.md +61 -0
  49. package/framework/.claude/skills/lc-build-codebase-knowledge/SKILL.md +38 -0
  50. package/framework/.claude/skills/lc-build-codebase-knowledge/prompts/build-codebase-knowledge.md +61 -0
  51. package/framework/.claude/skills/lc-collect-intelligence/SKILL.md +43 -0
  52. package/framework/.claude/skills/lc-collect-intelligence/prompts/main.md +72 -0
  53. package/framework/.claude/skills/lc-competitive-analysis/SKILL.md +43 -0
  54. package/framework/.claude/skills/lc-competitive-analysis/prompts/main.md +70 -0
  55. package/framework/.claude/skills/lc-explore-opensource/SKILL.md +36 -0
  56. package/framework/.claude/skills/lc-explore-opensource/prompts/explore-opensource.md +77 -0
  57. package/framework/.claude/skills/lc-first-principles/SKILL.md +43 -0
  58. package/framework/.claude/skills/lc-first-principles/prompts/main.md +73 -0
  59. package/framework/.claude/skills/lc-handoff/SKILL.md +34 -0
  60. package/framework/.claude/skills/lc-init-branch/SKILL.md +52 -0
  61. package/framework/.claude/skills/lc-market-research/SKILL.md +43 -0
  62. package/framework/.claude/skills/lc-market-research/prompts/main.md +79 -0
  63. package/framework/.claude/skills/lc-product-research/SKILL.md +43 -0
  64. package/framework/.claude/skills/lc-product-research/prompts/main.md +80 -0
  65. package/framework/.claude/skills/lc-research-report/SKILL.md +43 -0
  66. package/framework/.claude/skills/lc-research-report/prompts/main.md +90 -0
  67. package/framework/.claude/skills/lc-research-scope/SKILL.md +43 -0
  68. package/framework/.claude/skills/lc-research-scope/prompts/main.md +82 -0
  69. package/framework/.claude/skills/lc-setup/SKILL.md +68 -0
  70. package/framework/.claude/skills/lc-setup/prompts/main.md +88 -0
  71. package/framework/.claude/skills/lc-stage/SKILL.md +45 -0
  72. package/framework/.claude/skills/lc-stakeholder-research/SKILL.md +43 -0
  73. package/framework/.claude/skills/lc-stakeholder-research/prompts/main.md +65 -0
  74. package/framework/.claude/skills/lc-status/SKILL.md +25 -0
  75. package/framework/.claude/skills/lc-storytelling/SKILL.md +43 -0
  76. package/framework/.claude/skills/lc-storytelling/prompts/main.md +77 -0
  77. package/framework/.claude/skills/lc-wf/SKILL.md +54 -0
  78. package/framework/.claude/skills/lc-workflow/SKILL.md +48 -0
  79. package/framework/.claude/skills/lc-workflow/validators/validate.py +755 -0
  80. package/framework/.claude/skills/lc-workflow-router/SKILL.md +41 -0
  81. package/framework/.claude/skills/lc-workflow-router/prompts/intake-prompt.md +91 -0
  82. package/framework/.claude/skills/lc-workflow-router/prompts/router-prompt.md +44 -0
  83. package/framework/.claude/skills/plan-tdd-development/SKILL.md +31 -0
  84. package/framework/.claude/skills/plan-tdd-development/prompts/main.md +44 -0
  85. package/framework/.claude/skills/process-interview/SKILL.md +32 -0
  86. package/framework/.claude/skills/process-interview/prompts/main.md +47 -0
  87. package/framework/.claude/skills/propose-with-openspec/SKILL.md +37 -0
  88. package/framework/.claude/skills/propose-with-openspec/prompts/main.md +53 -0
  89. package/framework/.claude/skills/split-to-github/SKILL.md +32 -0
  90. package/framework/.claude/skills/split-to-github/prompts/main.md +42 -0
  91. package/framework/.claude/skills/sync-to-knowledge/SKILL.md +35 -0
  92. package/framework/.claude/skills/sync-to-knowledge/prompts/main.md +54 -0
  93. package/framework/.claude/skills/workflow-continue/SKILL.md +24 -0
  94. package/framework/.claude/skills/workflow-continue/prompts/main.md +25 -0
  95. package/framework/.claude/stages/build-codebase-knowledge.yaml +42 -0
  96. package/framework/.claude/stages/clarify.yaml +59 -0
  97. package/framework/.claude/stages/explore-opensource.yaml +40 -0
  98. package/framework/.claude/stages/implement.yaml +60 -0
  99. package/framework/.claude/stages/ingest.yaml +40 -0
  100. package/framework/.claude/stages/lc-analyze-frameworks.yaml +37 -0
  101. package/framework/.claude/stages/lc-collect-intelligence.yaml +37 -0
  102. package/framework/.claude/stages/lc-competitive-analysis.yaml +37 -0
  103. package/framework/.claude/stages/lc-first-principles.yaml +37 -0
  104. package/framework/.claude/stages/lc-market-research.yaml +37 -0
  105. package/framework/.claude/stages/lc-product-research.yaml +37 -0
  106. package/framework/.claude/stages/lc-research-report.yaml +38 -0
  107. package/framework/.claude/stages/lc-research-scope.yaml +38 -0
  108. package/framework/.claude/stages/lc-stakeholder-research.yaml +37 -0
  109. package/framework/.claude/stages/lc-storytelling.yaml +37 -0
  110. package/framework/.claude/stages/product-design-docs.yaml +66 -0
  111. package/framework/.claude/stages/product-prototype.yaml +53 -0
  112. package/framework/.claude/stages/propose.yaml +45 -0
  113. package/framework/.claude/stages/split.yaml +41 -0
  114. package/framework/.claude/stages/sync-knowledge.yaml +49 -0
  115. package/framework/.claude/stages/tdd-development-plan.yaml +43 -0
  116. package/framework/.claude/stages/workflow-router.yaml +45 -0
  117. package/framework/.claude/templates/solo-workflow-context.yaml +23 -0
  118. package/framework/.claude/workflows/README.md +93 -0
  119. package/framework/.claude/workflows/bug-fix.yaml +81 -0
  120. package/framework/.claude/workflows/design-spike.yaml +55 -0
  121. package/framework/.claude/workflows/existing-project-iteration.yaml +106 -0
  122. package/framework/.claude/workflows/interview-to-knowledge.yaml +189 -0
  123. package/framework/.claude/workflows/oss-first-design.yaml +100 -0
  124. package/framework/.claude/workflows/pm-research.yaml +115 -0
  125. package/framework/.claude-plugin/hooks.json +6 -0
  126. package/framework/.claude-plugin/marketplace.json +32 -0
  127. package/framework/.claude-plugin/plugin.json +57 -0
  128. package/framework/.version-bump.json +43 -0
  129. package/framework/CLAUDE.md +140 -0
  130. package/framework/CONTRIBUTING.md +219 -0
  131. package/framework/LICENSE +21 -0
  132. package/framework/README.en.md +139 -0
  133. package/framework/README.md +139 -0
  134. package/framework/RELEASE.md +334 -0
  135. package/framework/SKILL.md +24 -0
  136. package/framework/USAGE.md +356 -0
  137. package/framework/manifest.json +1214 -0
  138. package/framework/requirements.txt +4 -0
  139. package/framework/scripts/__init__.py +0 -0
  140. package/framework/scripts/build_codebase_knowledge.py +76 -0
  141. package/framework/scripts/bump_version.py +181 -0
  142. package/framework/scripts/check-dependency-drift.py +69 -0
  143. package/framework/scripts/check-harness-drift.sh +57 -0
  144. package/framework/scripts/check-main-merge-hygiene.py +128 -0
  145. package/framework/scripts/check-skill-dependencies.sh +124 -0
  146. package/framework/scripts/init-lincoln-branch.sh +300 -0
  147. package/framework/scripts/init-project.sh +184 -0
  148. package/framework/scripts/lc-benchmark-cli.py +66 -0
  149. package/framework/scripts/lincoln-audit.py +288 -0
  150. package/framework/scripts/lincoln-setup.py +651 -0
  151. package/framework/scripts/lincoln-status.py +448 -0
  152. package/framework/scripts/lincoln_benchmark.py +224 -0
  153. package/framework/scripts/lincoln_benchmark_eval.py +104 -0
  154. package/framework/scripts/lincoln_benchmark_metrics.py +818 -0
  155. package/framework/scripts/lincoln_benchmark_report.py +141 -0
  156. package/framework/scripts/lincoln_command_map.py +289 -0
  157. package/framework/scripts/lincoln_dependency_manager.py +335 -0
  158. package/framework/scripts/lincoln_documents.py +175 -0
  159. package/framework/scripts/lincoln_harness_adapter.py +319 -0
  160. package/framework/scripts/lincoln_index.py +255 -0
  161. package/framework/scripts/lincoln_paths.py +198 -0
  162. package/framework/scripts/lincoln_prd.py +227 -0
  163. package/framework/scripts/lincoln_role.py +46 -0
  164. package/framework/scripts/lincoln_scenario.py +59 -0
  165. package/framework/scripts/lincoln_session_start_metrics.py +71 -0
  166. package/framework/scripts/lincoln_skill_prompt.py +48 -0
  167. package/framework/scripts/lincoln_trace.py +335 -0
  168. package/framework/scripts/lincoln_workflow.py +199 -0
  169. package/framework/scripts/list-active-lincoln-branches.sh +87 -0
  170. package/framework/scripts/package-lincoln-plugin.py +340 -0
  171. package/framework/scripts/run-infrastructure-tests.py +84 -0
  172. package/framework/scripts/stage_loader.py +1071 -0
  173. package/framework/scripts/static-check.sh +356 -0
  174. package/framework/scripts/sync-external-agents.sh +210 -0
  175. package/framework/scripts/sync-framework-package.mjs +218 -0
  176. package/framework/scripts/task_tool_guard.py +213 -0
  177. package/framework/scripts/track-artifacts.py +140 -0
  178. package/framework/scripts/validate_stage.py +350 -0
  179. package/{README.md → framework/tools/lincoln/README.md} +4 -4
  180. package/framework/tools/lincoln/package-lock.json +3349 -0
  181. package/framework/tools/lincoln/package.json +48 -0
  182. package/framework/tools/lincoln/src/cli.tsx +60 -0
  183. package/framework/tools/lincoln/src/components/AudioMeter.tsx +34 -0
  184. package/framework/tools/lincoln/src/components/CancelledScreen.tsx +11 -0
  185. package/framework/tools/lincoln/src/components/DevicesMenu.tsx +79 -0
  186. package/framework/tools/lincoln/src/components/HintBar.tsx +25 -0
  187. package/framework/tools/lincoln/src/components/LogView.tsx +36 -0
  188. package/framework/tools/lincoln/src/components/ModelMenu.tsx +122 -0
  189. package/framework/tools/lincoln/src/components/ReadyScreen.tsx +54 -0
  190. package/framework/tools/lincoln/src/components/RecordingApp.tsx +211 -0
  191. package/framework/tools/lincoln/src/components/RecordingScreen.tsx +41 -0
  192. package/framework/tools/lincoln/src/components/SelectMenu.tsx +53 -0
  193. package/framework/tools/lincoln/src/components/StatusPanel.tsx +52 -0
  194. package/framework/tools/lincoln/src/components/StopConfirmation.tsx +32 -0
  195. package/framework/tools/lincoln/src/config/args.ts +35 -0
  196. package/framework/tools/lincoln/src/config/loadConfig.ts +81 -0
  197. package/framework/tools/lincoln/src/config/resolveConfig.ts +85 -0
  198. package/framework/tools/lincoln/src/devices/listDevices.ts +11 -0
  199. package/framework/tools/lincoln/src/devices/parseDevices.ts +25 -0
  200. package/framework/tools/lincoln/src/hooks/useKeyHandler.ts +63 -0
  201. package/framework/tools/lincoln/src/main.tsx +4 -0
  202. package/framework/tools/lincoln/src/models/models.ts +34 -0
  203. package/framework/tools/lincoln/src/models/runWarmup.ts +46 -0
  204. package/framework/tools/lincoln/src/models/warmupProgress.ts +30 -0
  205. package/framework/tools/lincoln/src/recording/spawnRecorder.ts +127 -0
  206. package/framework/tools/lincoln/src/recording/useRecorder.ts +146 -0
  207. package/framework/tools/lincoln/src/utils/amplitudeToMeter.ts +4 -0
  208. package/framework/tools/lincoln/src/utils/formatDuration.ts +6 -0
  209. package/framework/tools/lincoln/src/utils/sessionId.ts +25 -0
  210. package/framework/tools/lincoln/tests/cli.test.tsx +52 -0
  211. package/framework/tools/lincoln/tests/components/AudioMeter.test.tsx +31 -0
  212. package/framework/tools/lincoln/tests/components/CancelledScreen.test.tsx +12 -0
  213. package/framework/tools/lincoln/tests/components/ReadyScreen.test.tsx +68 -0
  214. package/framework/tools/lincoln/tests/components/RecordingApp.menus.test.tsx +158 -0
  215. package/framework/tools/lincoln/tests/components/RecordingApp.test.tsx +248 -0
  216. package/framework/tools/lincoln/tests/components/RecordingScreen.test.tsx +37 -0
  217. package/framework/tools/lincoln/tests/components/SelectMenu.test.tsx +59 -0
  218. package/framework/tools/lincoln/tests/components/StopConfirmation.test.tsx +38 -0
  219. package/framework/tools/lincoln/tests/config/args.test.ts +49 -0
  220. package/framework/tools/lincoln/tests/config/loadConfig.test.ts +59 -0
  221. package/framework/tools/lincoln/tests/config/resolveConfig.test.ts +72 -0
  222. package/framework/tools/lincoln/tests/devices/parseDevices.test.ts +21 -0
  223. package/framework/tools/lincoln/tests/hooks/useKeyHandler.enabled.test.tsx +44 -0
  224. package/framework/tools/lincoln/tests/hooks/useKeyHandler.test.tsx +130 -0
  225. package/framework/tools/lincoln/tests/models/models.test.ts +27 -0
  226. package/framework/tools/lincoln/tests/models/warmupProgress.test.ts +25 -0
  227. package/framework/tools/lincoln/tests/recording/spawnRecorder.test.ts +85 -0
  228. package/framework/tools/lincoln/tests/recording/useRecorder.test.tsx +172 -0
  229. package/framework/tools/lincoln/tests/utils/amplitudeToMeter.test.ts +22 -0
  230. package/framework/tools/lincoln/tests/utils/formatDuration.test.ts +11 -0
  231. package/framework/tools/lincoln/tests/utils/sessionId.test.ts +30 -0
  232. package/framework/tools/lincoln/tsconfig.json +22 -0
  233. package/framework/tools/lincoln/tsconfig.tests.json +10 -0
  234. package/framework/tools/lincoln/tsup.config.ts +17 -0
  235. package/framework/tools/lincoln/vitest.config.ts +20 -0
  236. package/framework/tools/lincoln-installer/package-lock.json +3611 -0
  237. package/framework/tools/lincoln-installer/package.json +53 -0
  238. package/framework/tools/lincoln-installer/src/cli.tsx +63 -0
  239. package/framework/tools/lincoln-installer/src/components/HarnessSelectScreen.tsx +43 -0
  240. package/framework/tools/lincoln-installer/src/components/InstallerApp.tsx +98 -0
  241. package/framework/tools/lincoln-installer/src/components/OptionsScreen.tsx +56 -0
  242. package/framework/tools/lincoln-installer/src/components/ProgressScreen.tsx +25 -0
  243. package/framework/tools/lincoln-installer/src/components/ResultScreen.tsx +40 -0
  244. package/framework/tools/lincoln-installer/src/components/SelectMenu.tsx +73 -0
  245. package/framework/tools/lincoln-installer/src/components/SummaryScreen.tsx +49 -0
  246. package/framework/tools/lincoln-installer/src/components/UpdateApp.tsx +135 -0
  247. package/framework/tools/lincoln-installer/src/components/WelcomeScreen.tsx +22 -0
  248. package/framework/tools/lincoln-installer/src/config/args.ts +76 -0
  249. package/framework/tools/lincoln-installer/src/config/resolveConfig.ts +33 -0
  250. package/framework/tools/lincoln-installer/src/config/updateArgs.ts +67 -0
  251. package/framework/tools/lincoln-installer/src/hooks/useSetup.ts +149 -0
  252. package/framework/tools/lincoln-installer/src/main.tsx +11 -0
  253. package/framework/tools/lincoln-installer/src/types.ts +19 -0
  254. package/framework/tools/lincoln-installer/src/update-main.tsx +11 -0
  255. package/framework/tools/lincoln-installer/src/update.tsx +90 -0
  256. package/framework/tools/lincoln-installer/src/utils/detectHarness.ts +51 -0
  257. package/framework/tools/lincoln-installer/src/utils/runSetup.ts +62 -0
  258. package/framework/tools/lincoln-installer/src/utils/updateRelease.ts +454 -0
  259. package/framework/tools/lincoln-installer/tests/components/InstallerApp.test.tsx +88 -0
  260. package/framework/tools/lincoln-installer/tests/components/SelectMenu.test.tsx +26 -0
  261. package/framework/tools/lincoln-installer/tests/components/UpdateApp.test.tsx +170 -0
  262. package/framework/tools/lincoln-installer/tests/components/screens.test.tsx +310 -0
  263. package/framework/tools/lincoln-installer/tests/config/args.test.ts +66 -0
  264. package/framework/tools/lincoln-installer/tests/config/resolveConfig.test.ts +75 -0
  265. package/framework/tools/lincoln-installer/tests/config/updateArgs.test.ts +52 -0
  266. package/framework/tools/lincoln-installer/tests/hooks/useSetup.test.tsx +213 -0
  267. package/framework/tools/lincoln-installer/tests/update.test.tsx +108 -0
  268. package/framework/tools/lincoln-installer/tests/utils/detectHarness.test.ts +40 -0
  269. package/framework/tools/lincoln-installer/tests/utils/runSetup.test.ts +45 -0
  270. package/framework/tools/lincoln-installer/tests/utils/updateRelease.test.ts +559 -0
  271. package/framework/tools/lincoln-installer/tsconfig.json +22 -0
  272. package/framework/tools/lincoln-installer/tsconfig.tests.json +8 -0
  273. package/framework/tools/lincoln-installer/tsup.config.ts +17 -0
  274. package/framework/tools/lincoln-installer/vitest.config.ts +20 -0
  275. package/framework/tools/lincoln-record/Cargo.lock +1896 -0
  276. package/framework/tools/lincoln-record/Cargo.toml +24 -0
  277. package/framework/tools/lincoln-record/diarize/diarize.py +117 -0
  278. package/framework/tools/lincoln-record/diarize/merge.py +55 -0
  279. package/framework/tools/lincoln-record/src/audio/capture/microphone.rs +194 -0
  280. package/framework/tools/lincoln-record/src/audio/capture/mod.rs +1 -0
  281. package/framework/tools/lincoln-record/src/audio/devices.rs +20 -0
  282. package/framework/tools/lincoln-record/src/audio/mod.rs +5 -0
  283. package/framework/tools/lincoln-record/src/audio/resampler.rs +44 -0
  284. package/framework/tools/lincoln-record/src/audio/saver.rs +87 -0
  285. package/framework/tools/lincoln-record/src/audio/source.rs +104 -0
  286. package/framework/tools/lincoln-record/src/cli.rs +96 -0
  287. package/framework/tools/lincoln-record/src/config.rs +135 -0
  288. package/framework/tools/lincoln-record/src/lib.rs +6 -0
  289. package/framework/tools/lincoln-record/src/main.rs +242 -0
  290. package/framework/tools/lincoln-record/src/model/mod.rs +248 -0
  291. package/framework/tools/lincoln-record/src/output/metadata.rs +30 -0
  292. package/framework/tools/lincoln-record/src/output/mod.rs +2 -0
  293. package/framework/tools/lincoln-record/src/output/transcript.rs +42 -0
  294. package/framework/tools/lincoln-record/src/transcription/diarization.rs +64 -0
  295. package/framework/tools/lincoln-record/src/transcription/mod.rs +3 -0
  296. package/framework/tools/lincoln-record/src/transcription/provider.rs +43 -0
  297. package/framework/tools/lincoln-record/src/transcription/whisper.rs +176 -0
  298. package/framework/tools/lincoln-record/tests/cli_args.rs +103 -0
  299. package/framework/tools/lincoln-record/tests/config_test.rs +70 -0
  300. package/framework/tools/lincoln-record/tests/devices_test.rs +23 -0
  301. package/framework/tools/lincoln-record/tests/diarization_tests.rs +101 -0
  302. package/framework/tools/lincoln-record/tests/integration_tests.rs +44 -0
  303. package/framework/tools/lincoln-record/tests/model_test.rs +68 -0
  304. package/framework/tools/lincoln-record/tests/transcription_tests.rs +73 -0
  305. package/package.json +19 -13
@@ -0,0 +1,319 @@
1
+ #!/usr/bin/env python3
2
+ """Harness adapter generator for Lincoln.
3
+
4
+ Derives codex/opencode artifacts (AGENTS.md, prompts, agents, commands) from
5
+ the single source of truth in `.claude/`, driven by `.claude/harnesses/<name>.yaml`
6
+ manifests. Idempotent; `--check` regenerates into a temp dir and reports drift.
7
+
8
+ Usage:
9
+ python3 scripts/lincoln_harness_adapter.py --harness opencode
10
+ python3 scripts/lincoln_harness_adapter.py --harness codex --check
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import argparse
16
+ import json
17
+ import sys
18
+ import tempfile
19
+ from pathlib import Path
20
+ from typing import Any
21
+
22
+ import yaml
23
+
24
+ GENERATED_NOTE = "自动生成,请勿手改"
25
+ GENERATOR = "scripts/lincoln_harness_adapter.py"
26
+
27
+ GATE_CLAUSE = """## 门控约束(Lincoln lightweight gate)
28
+
29
+ 阶段推进前必须运行准入/准出校验:
30
+
31
+ ```
32
+ python3 scripts/stage_loader.py --stage <current_stage> --action validate-entry
33
+ python3 scripts/stage_loader.py --stage <current_stage> --action validate-exit
34
+ ```
35
+
36
+ human_gate 阶段必须获得人类 PM 显式确认后才可推进,不得跳过。"""
37
+
38
+ FRONTMATTER_WHITELIST = ("description", "mode", "model", "permission", "tools")
39
+
40
+
41
+ class HarnessAdapterError(Exception):
42
+ """Raised when a harness manifest is missing or invalid."""
43
+
44
+
45
+ def load_manifest(root: Path, name: str) -> dict[str, Any]:
46
+ path = root / ".claude" / "harnesses" / f"{name}.yaml"
47
+ if not path.exists():
48
+ raise HarnessAdapterError(f"Harness manifest not found: {path}")
49
+ data = yaml.safe_load(path.read_text(encoding="utf-8"))
50
+ if not isinstance(data, dict):
51
+ raise HarnessAdapterError(f"Invalid manifest (not a mapping): {path}")
52
+ if "command_map" not in data and "command_map_source" in data:
53
+ source_path = path.parent / data["command_map_source"]
54
+ if not source_path.exists():
55
+ raise HarnessAdapterError(f"command_map_source not found: {source_path}")
56
+ shared = yaml.safe_load(source_path.read_text(encoding="utf-8")) or {}
57
+ commands = shared.get("commands")
58
+ if not isinstance(commands, dict):
59
+ raise HarnessAdapterError(f"command_map_source missing 'commands': {source_path}")
60
+ data["command_map"] = commands
61
+ errors = validate_manifest(data)
62
+ if errors:
63
+ raise HarnessAdapterError(f"Invalid manifest {path}: {'; '.join(errors)}")
64
+ return data
65
+
66
+
67
+ def validate_manifest(manifest: dict[str, Any]) -> list[str]:
68
+ errors: list[str] = []
69
+ if not manifest.get("name"):
70
+ errors.append("missing field: name")
71
+ capabilities = manifest.get("capabilities")
72
+ if not isinstance(capabilities, dict):
73
+ errors.append("missing field: capabilities")
74
+ else:
75
+ for key in ("hooks", "skills", "agents", "commands"):
76
+ if not isinstance(capabilities.get(key), bool):
77
+ errors.append(f"capabilities.{key} must be an explicit boolean")
78
+ if not isinstance(manifest.get("targets"), list):
79
+ errors.append("missing field: targets")
80
+ command_map = manifest.get("command_map")
81
+ if not isinstance(command_map, dict) or not command_map:
82
+ errors.append("missing field: command_map")
83
+ else:
84
+ for key in command_map:
85
+ if not key.startswith("lc-"):
86
+ errors.append(f"command_map key must start with 'lc-': {key}")
87
+ return errors
88
+
89
+
90
+ def _header(harness: str, source: str) -> str:
91
+ return f"<!-- {GENERATED_NOTE} (generated by {GENERATOR}; harness: {harness}; source: {source}) -->"
92
+
93
+
94
+ def _resolve_output(template: str, name: str, project_dir: Path, home_dir: Path) -> Path:
95
+ rendered = template.format(
96
+ project=str(project_dir), home=str(home_dir), name=name
97
+ )
98
+ return Path(rendered)
99
+
100
+
101
+ def _split_frontmatter(text: str) -> tuple[dict[str, Any], str]:
102
+ if not text.startswith("---"):
103
+ return {}, text
104
+ parts = text.split("---", 2)
105
+ if len(parts) < 3:
106
+ return {}, text
107
+ try:
108
+ front = yaml.safe_load(parts[1]) or {}
109
+ except yaml.YAMLError:
110
+ front = {}
111
+ if not isinstance(front, dict):
112
+ front = {}
113
+ return front, parts[2].lstrip("\n")
114
+
115
+
116
+ def _transform_frontmatter(
117
+ text: str, harness: str, source: str, fields: tuple[str, ...] = FRONTMATTER_WHITELIST
118
+ ) -> str:
119
+ front, body = _split_frontmatter(text)
120
+ kept = {k: v for k, v in front.items() if k in fields}
121
+ if "mode" not in kept:
122
+ kept["mode"] = "subagent"
123
+ yaml_front = yaml.safe_dump(kept, allow_unicode=True, sort_keys=False).strip()
124
+ return f"---\n{yaml_front}\n---\n\n{_header(harness, source)}\n\n{body}"
125
+
126
+
127
+ def _transform_copy(text: str, harness: str, source: str) -> str:
128
+ return f"{_header(harness, source)}\n\n{text}"
129
+
130
+
131
+ # Metadata fields kept when deriving a codex/opencode plugin manifest.
132
+ _PLUGIN_METADATA_KEYS = (
133
+ "name",
134
+ "version",
135
+ "description",
136
+ "author",
137
+ "repository",
138
+ "homepage",
139
+ "license",
140
+ "keywords",
141
+ )
142
+
143
+
144
+ def _transform_plugin_json(text: str, harness: str, source: str) -> str:
145
+ """Derive a plugin.json manifest with explicit empty capability declarations.
146
+
147
+ Lesson from superpowers (obra/superpowers@7d8d3d4): Codex falls back to
148
+ hooks/hooks.json when the manifest omits the `hooks` field. We therefore
149
+ keep only neutral metadata, drop harness-incompatible blocks, and emit
150
+ `"hooks": {}` explicitly rather than omitting the field.
151
+ """
152
+ try:
153
+ original = json.loads(text)
154
+ except json.JSONDecodeError as exc:
155
+ raise HarnessAdapterError(f"Invalid plugin JSON ({source}): {exc}") from exc
156
+ if not isinstance(original, dict):
157
+ raise HarnessAdapterError(f"Plugin JSON must be an object: {source}")
158
+
159
+ manifest = {k: original[k] for k in _PLUGIN_METADATA_KEYS if k in original}
160
+ # Explicitly disable hooks so the harness does not fall back to defaults.
161
+ manifest["hooks"] = {}
162
+ return json.dumps(manifest, indent=2, ensure_ascii=False) + "\n"
163
+
164
+
165
+ def _transform_concat(
166
+ texts: list[tuple[str, str]], harness: str, sources: str, manifest: dict[str, Any]
167
+ ) -> str:
168
+ parts = [_header(harness, sources), ""]
169
+ for title, text in texts:
170
+ parts.append(f"## {title}\n\n{text.strip()}\n")
171
+ command_map = manifest.get("command_map", {})
172
+ if command_map:
173
+ parts.append("## 命令(lc-*)\n")
174
+ for cmd_name, cmd in command_map.items():
175
+ description = cmd.get("description", cmd_name)
176
+ action = cmd.get("action", "")
177
+ args_hint = cmd.get("args_hint", "")
178
+ parts.append(f"- `{cmd_name}` — {description}。执行: `{action}` {args_hint}".rstrip())
179
+ parts.append(f"\n{GATE_CLAUSE}\n")
180
+ return "\n".join(parts)
181
+
182
+
183
+ def _transform_command_template(cmd_name: str, cmd: dict[str, Any], harness: str) -> str:
184
+ description = cmd.get("description", cmd_name)
185
+ action = cmd.get("action", "")
186
+ args_hint = cmd.get("args_hint", "")
187
+ body = (
188
+ f"---\ndescription: {description}\n---\n\n"
189
+ f"{_header(harness, 'command_map')}\n\n"
190
+ f"执行: `{action}` {args_hint}\n\n"
191
+ f"{GATE_CLAUSE}\n\n"
192
+ "用户输入: $ARGUMENTS\n"
193
+ )
194
+ return body
195
+
196
+
197
+ def _expand_target(
198
+ root: Path, target: dict[str, Any], manifest: dict[str, Any]
199
+ ) -> list[tuple[str, str, str]]:
200
+ """Expand a target into (output_name, content, source_label) tuples."""
201
+ kind = target.get("kind")
202
+ transform = target.get("transform", "copy")
203
+ source = target.get("source", "")
204
+ harness = manifest["name"]
205
+
206
+ if source == "command_map" or kind == "command":
207
+ items = []
208
+ for cmd_name, cmd in manifest.get("command_map", {}).items():
209
+ content = _transform_command_template(cmd_name, cmd, harness)
210
+ items.append((cmd_name, content, "command_map"))
211
+ return items
212
+
213
+ sources = sorted(root.glob(source))
214
+ if not sources:
215
+ raise HarnessAdapterError(f"Target source matched nothing: {source}")
216
+
217
+ if transform == "concat":
218
+ texts = [(p.stem, p.read_text(encoding="utf-8")) for p in sources]
219
+ return [("__concat__", _transform_concat(texts, harness, source, manifest), source)]
220
+
221
+ items = []
222
+ for path in sources:
223
+ text = path.read_text(encoding="utf-8")
224
+ if transform == "frontmatter":
225
+ fields = tuple(target.get("fields", FRONTMATTER_WHITELIST))
226
+ content = _transform_frontmatter(
227
+ text, harness, str(path.relative_to(root)), fields
228
+ )
229
+ elif transform == "plugin-json":
230
+ content = _transform_plugin_json(
231
+ text, harness, str(path.relative_to(root))
232
+ )
233
+ else:
234
+ content = _transform_copy(text, harness, str(path.relative_to(root)))
235
+ items.append((path.stem, content, str(path.relative_to(root))))
236
+ return items
237
+
238
+
239
+ def generate(
240
+ root: Path, harness: str, project_dir: Path, home_dir: Path
241
+ ) -> list[Path]:
242
+ """Generate all artifacts for a harness. Returns written paths."""
243
+ manifest = load_manifest(root, harness)
244
+ written: list[Path] = []
245
+ staging: list[tuple[Path, str]] = []
246
+
247
+ for target in manifest["targets"]:
248
+ for name, content, _source in _expand_target(root, target, manifest):
249
+ out_path = _resolve_output(target["output"], name, project_dir, home_dir)
250
+ staging.append((out_path, content))
251
+
252
+ # Write via temp files then move (atomic per file).
253
+ for out_path, content in staging:
254
+ out_path.parent.mkdir(parents=True, exist_ok=True)
255
+ tmp = out_path.with_suffix(out_path.suffix + ".tmp")
256
+ tmp.write_text(content, encoding="utf-8")
257
+ tmp.replace(out_path)
258
+ written.append(out_path)
259
+ return written
260
+
261
+
262
+ def check_drift(
263
+ root: Path, harness: str, project_dir: Path, home_dir: Path
264
+ ) -> list[str]:
265
+ """Regenerate into a temp dir and compare with existing artifacts."""
266
+ diffs: list[str] = []
267
+ with tempfile.TemporaryDirectory() as tmp:
268
+ tmp_project = Path(tmp) / "project"
269
+ tmp_home = Path(tmp) / "home"
270
+ tmp_project.mkdir()
271
+ tmp_home.mkdir()
272
+ staged = generate(root, harness, tmp_project, tmp_home)
273
+ for temp_path in staged:
274
+ if str(temp_path).startswith(str(tmp_project)):
275
+ real_path = project_dir / temp_path.relative_to(tmp_project)
276
+ else:
277
+ real_path = home_dir / temp_path.relative_to(tmp_home)
278
+ if not real_path.exists():
279
+ diffs.append(f"missing: {real_path}")
280
+ elif real_path.read_text(encoding="utf-8") != temp_path.read_text(encoding="utf-8"):
281
+ diffs.append(f"modified: {real_path}")
282
+ return diffs
283
+
284
+
285
+ def main(argv: list[str] | None = None) -> int:
286
+ parser = argparse.ArgumentParser(description="Lincoln harness adapter generator")
287
+ parser.add_argument("--harness", required=True, help="Harness name (codex|opencode|claude-code)")
288
+ parser.add_argument("--root", type=Path, default=Path.cwd())
289
+ parser.add_argument("--project-dir", type=Path, default=Path.cwd())
290
+ parser.add_argument("--home-dir", type=Path, default=Path.home())
291
+ parser.add_argument("--check", action="store_true", help="Drift check only; no writes")
292
+ args = parser.parse_args(argv)
293
+
294
+ try:
295
+ if args.check:
296
+ diffs = check_drift(args.root, args.harness, args.project_dir, args.home_dir)
297
+ if diffs:
298
+ print(f"Drift detected for harness '{args.harness}':")
299
+ for d in diffs:
300
+ print(f" - {d}")
301
+ print(
302
+ "Run: python3 scripts/lincoln_harness_adapter.py "
303
+ f"--harness {args.harness}"
304
+ )
305
+ return 1
306
+ print(f"No drift for harness '{args.harness}'.")
307
+ return 0
308
+ written = generate(args.root, args.harness, args.project_dir, args.home_dir)
309
+ print(f"Generated {len(written)} artifacts for harness '{args.harness}':")
310
+ for path in written:
311
+ print(f" - {path}")
312
+ return 0
313
+ except HarnessAdapterError as exc:
314
+ print(f"ERROR: {exc}", file=sys.stderr)
315
+ return 2
316
+
317
+
318
+ if __name__ == "__main__":
319
+ sys.exit(main())
@@ -0,0 +1,255 @@
1
+ #!/usr/bin/env python3
2
+ """Maintain the per-package HTML portal index.
3
+
4
+ Generates {process_slug}/assets/js/package-data.js from workflow-stage.yaml and a
5
+ scan of {process_slug}/pages/**/*.html. The portal's index.html loads this file
6
+ and renders navigation, status, and annotations.
7
+
8
+ Replaces the older documents.yaml index; machine state stays in
9
+ workflow-stage.yaml, while package-data.js is a generated projection for humans.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import argparse
15
+ import json
16
+ import re
17
+ import sys
18
+ from datetime import datetime, timezone
19
+ from html.parser import HTMLParser
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
+ from scripts.lincoln_paths import STATE_FILENAME, is_process_state_path
27
+
28
+ PACKAGE_DATA_FILENAME = "assets/js/package-data.js"
29
+ VERSION_COMMENT_RE = re.compile(r"<!--\s*version:\s*(v\d+\.\d+)\s*-->", re.IGNORECASE)
30
+ STATUS_COMMENT_RE = re.compile(r"<!--\s*status:\s*(\w+)\s*-->", re.IGNORECASE)
31
+ LIST_META_KEYS = ("doc-fields", "doc-stories", "doc-rules", "doc-boundaries", "doc-exceptions", "doc-refs")
32
+ LIST_SEPARATOR = "|"
33
+
34
+
35
+ class MetaParser(HTMLParser):
36
+ """Collect <meta name="..." content="..."> tags and the <title>."""
37
+
38
+ def __init__(self) -> None:
39
+ super().__init__(convert_charrefs=True)
40
+ self.meta: dict[str, str] = {}
41
+ self.title: str = ""
42
+ self._in_title = False
43
+
44
+ def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None:
45
+ if tag == "meta":
46
+ attr_dict = {k: v or "" for k, v in attrs}
47
+ name = attr_dict.get("name", "").strip()
48
+ content = attr_dict.get("content", "").strip()
49
+ if name:
50
+ self.meta[name] = content
51
+ if tag == "title":
52
+ self._in_title = True
53
+
54
+ def handle_endtag(self, tag: str) -> None:
55
+ if tag == "title":
56
+ self._in_title = False
57
+
58
+ def handle_data(self, data: str) -> None:
59
+ if self._in_title:
60
+ self.title += data
61
+
62
+
63
+ def now_iso() -> str:
64
+ return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
65
+
66
+
67
+ def package_relative(path: str, process_slug: str) -> str:
68
+ """Strip the '<process_slug>/' prefix from a repo-relative artifact path."""
69
+ prefix = f"{process_slug}/"
70
+ return path[len(prefix) :] if path.startswith(prefix) else path
71
+
72
+
73
+ def split_list_meta(value: str) -> list[str]:
74
+ """Split a pipe-separated meta value into a list of trimmed non-empty items."""
75
+ if not value:
76
+ return []
77
+ return [item.strip() for item in value.split(LIST_SEPARATOR) if item.strip()]
78
+
79
+
80
+ def extract_html_meta(full_path: Path) -> dict[str, Any]:
81
+ """Parse meta tags and version/status comments from an HTML page."""
82
+ if not full_path.exists():
83
+ return {}
84
+ text = full_path.read_text(encoding="utf-8")
85
+ parser = MetaParser()
86
+ try:
87
+ parser.feed(text)
88
+ except Exception:
89
+ pass
90
+
91
+ version_match = VERSION_COMMENT_RE.search(text)
92
+ status_match = STATUS_COMMENT_RE.search(text)
93
+ meta = {
94
+ "title": parser.meta.get("doc-title") or parser.title or full_path.stem,
95
+ "nav_label": parser.meta.get("nav-label", ""),
96
+ "nav_group": parser.meta.get("nav-group", "Docs"),
97
+ "version": parser.meta.get("doc-version") or (version_match.group(1) if version_match else None),
98
+ "status": status_match.group(1) if status_match else "",
99
+ "uid": parser.meta.get("doc-uid") or parser.meta.get("page-uid", ""),
100
+ "purpose": parser.meta.get("doc-purpose", ""),
101
+ "layout": parser.meta.get("doc-layout", ""),
102
+ }
103
+ for key in LIST_META_KEYS:
104
+ field = key.replace("doc-", "")
105
+ meta[field] = split_list_meta(parser.meta.get(key, ""))
106
+ return meta
107
+
108
+
109
+ def extract_html_markdown(full_path: Path) -> str:
110
+ """Return the embedded Markdown source from a Lincoln doc HTML page.
111
+
112
+ If the page does not contain a `<script id="docSource" type="text/markdown">`
113
+ block, fall back to the raw file text so that legacy or migrated pages still
114
+ pass content-based validators.
115
+ """
116
+ if not full_path.exists():
117
+ return ""
118
+ text = full_path.read_text(encoding="utf-8")
119
+ match = re.search(
120
+ r'<script(?=\s)([^>]*?\s(?:id="docSource"|type="text/markdown")[^>]*?)>(.*?)</script>',
121
+ text,
122
+ re.DOTALL | re.IGNORECASE,
123
+ )
124
+ if match:
125
+ source = match.group(2)
126
+ return source.strip()
127
+ return text.strip()
128
+
129
+
130
+ def scan_pages(project_root: Path, process_slug: str) -> dict[str, dict[str, Any]]:
131
+ """Return a dict keyed by page path with metadata from each HTML page."""
132
+ pages_dir = project_root / process_slug / "pages"
133
+ found: dict[str, dict[str, Any]] = {}
134
+ if not pages_dir.is_dir():
135
+ return found
136
+ for path in sorted(pages_dir.rglob("*.html")):
137
+ rel = path.relative_to(project_root / process_slug).as_posix()
138
+ found[rel] = extract_html_meta(path)
139
+ return found
140
+
141
+
142
+ def build_package_data(
143
+ state: dict[str, Any],
144
+ process_slug: str,
145
+ generated_at: str | None = None,
146
+ project_root: Path | None = None,
147
+ ) -> dict[str, Any]:
148
+ """Build the portal data object from state and scanned HTML pages."""
149
+ root = project_root or Path(__file__).resolve().parents[1]
150
+ current_run = state.get("current_run", {})
151
+
152
+ # Start with scanned pages; merge state node metadata over them.
153
+ page_data = scan_pages(root, process_slug)
154
+
155
+ for node in state.get("nodes") or []:
156
+ stage_id = str(node.get("stage_id") or "")
157
+ status = str(node.get("status") or "")
158
+ gate_passed = bool(node.get("gate_passed"))
159
+ approver = node.get("approved_by")
160
+ human_confirmed = bool(approver and str(approver).startswith("human"))
161
+
162
+ for artifact in node.get("artifacts") or []:
163
+ rel = package_relative(str(artifact), process_slug)
164
+ if not rel.endswith(".html"):
165
+ continue
166
+ entry = page_data.setdefault(rel, {})
167
+ entry.setdefault("path", rel)
168
+ entry.setdefault("title", entry.get("title", Path(rel).stem))
169
+ entry.setdefault("label", entry.get("title", Path(rel).stem))
170
+ entry.setdefault("group", entry.get("nav_group", "Docs"))
171
+ entry["stage"] = stage_id or entry.get("stage", "")
172
+ entry["status"] = status or entry.get("status", "")
173
+ entry["gate_passed"] = gate_passed or entry.get("gate_passed", False)
174
+ entry["human_confirmed"] = human_confirmed or entry.get("human_confirmed", False)
175
+
176
+ # Build nav groups preserving scan order.
177
+ groups: dict[str, list[dict[str, Any]]] = {}
178
+ for rel in sorted(page_data):
179
+ entry = page_data[rel]
180
+ group_name = entry.get("group") or entry.get("nav_group") or "Docs"
181
+ groups.setdefault(group_name, []).append(
182
+ {
183
+ "path": rel,
184
+ "label": entry.get("nav_label") or entry.get("label") or entry.get("title") or Path(rel).stem,
185
+ "title": entry.get("title") or Path(rel).stem,
186
+ "version": entry.get("version"),
187
+ "status": entry.get("status", ""),
188
+ "stage": entry.get("stage", ""),
189
+ "gate_passed": entry.get("gate_passed", False),
190
+ "human_confirmed": entry.get("human_confirmed", False),
191
+ "purpose": entry.get("purpose", ""),
192
+ "layout": entry.get("layout", ""),
193
+ "stories": entry.get("stories", []),
194
+ "fields": entry.get("fields", []),
195
+ "rules": entry.get("rules", []),
196
+ "boundaries": entry.get("boundaries", []),
197
+ "exceptions": entry.get("exceptions", []),
198
+ "refs": entry.get("refs", []),
199
+ "uid": entry.get("uid", ""),
200
+ }
201
+ )
202
+
203
+ nav = [{"group": g, "items": items} for g, items in groups.items()]
204
+
205
+ return {
206
+ "process_slug": process_slug,
207
+ "issue_number": str(current_run.get("issue_number") or ""),
208
+ "current_stage": str(current_run.get("current_stage") or ""),
209
+ "status": str(current_run.get("status") or ""),
210
+ "generated_at": generated_at or now_iso(),
211
+ "nav": nav,
212
+ }
213
+
214
+
215
+ def write_package_index(
216
+ state: dict[str, Any],
217
+ state_path: Path,
218
+ generated_at: str | None = None,
219
+ ) -> Path | None:
220
+ """Regenerate package-data.js next to a team package state file."""
221
+ if not is_process_state_path(state_path):
222
+ return None
223
+ project_root = state_path.parents[1]
224
+ process_slug = state_path.parent.name
225
+ data = build_package_data(state, process_slug, generated_at, project_root)
226
+
227
+ package_data_dir = state_path.parent / "assets" / "js"
228
+ package_data_dir.mkdir(parents=True, exist_ok=True)
229
+ index_path = package_data_dir / "package-data.js"
230
+
231
+ js = "window.LINC_PACKAGE = " + json.dumps(data, ensure_ascii=False, indent=2) + ";\n"
232
+ index_path.write_text(js, encoding="utf-8")
233
+ return index_path
234
+
235
+
236
+ def main() -> int:
237
+ parser = argparse.ArgumentParser(description="Regenerate the issue-package HTML portal index")
238
+ parser.add_argument("--state-file", required=True, help="Path to the package workflow-stage.yaml")
239
+ args = parser.parse_args()
240
+
241
+ state_path = Path(args.state_file).resolve()
242
+ if not state_path.is_file():
243
+ print(f"ERROR: state file not found: {state_path}", file=sys.stderr)
244
+ return 1
245
+ state = yaml.safe_load(state_path.read_text(encoding="utf-8"))
246
+ index_path = write_package_index(state, state_path)
247
+ if index_path is None:
248
+ print(f"SKIP: {state_path} is not a team issue-package state file")
249
+ return 0
250
+ print(f"Updated {index_path}")
251
+ return 0
252
+
253
+
254
+ if __name__ == "__main__":
255
+ sys.exit(main())