@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,335 @@
1
+ #!/usr/bin/env python3
2
+ """Dependency manager for Lincoln setup automation.
3
+
4
+ Reads `.claude/skills/dependencies.yaml` as the single source of truth and
5
+ provides functions to check, install, and configure external skills, CLIs,
6
+ and repository settings used by the Lincoln workflow.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import platform
12
+ import re
13
+ import shlex
14
+ import shutil
15
+ import subprocess
16
+ from pathlib import Path
17
+ from typing import Any
18
+
19
+ import yaml
20
+
21
+
22
+ # Placeholder values that signal an unconfigured OpenSpec config file.
23
+ _PLACEHOLDER_OWNERS = {"your-org", "sushanglewis"}
24
+ _PLACEHOLDER_NAMES = {"your-repo", "your-product-repo", "product-manager"}
25
+
26
+ _DEFAULT_OPENSPEC_CONFIG = {
27
+ "repository": {
28
+ "owner": "your-org",
29
+ "name": "your-product-repo",
30
+ "default_branch": "main",
31
+ },
32
+ "workflow": {
33
+ "auto_create_issues": True,
34
+ "default_labels": ["from-interview", "openspec"],
35
+ "knowledge_sync": {"enabled": True, "actor": "github-actions"},
36
+ },
37
+ "openspec": {"profile": "default", "telemetry": False},
38
+ }
39
+
40
+
41
+ def load_dependencies(project_root: Path) -> dict[str, Any]:
42
+ """Load and parse `.claude/skills/dependencies.yaml`."""
43
+ dep_file = project_root / ".claude" / "skills" / "dependencies.yaml"
44
+ if not dep_file.exists():
45
+ raise FileNotFoundError(f"Dependency manifest not found: {dep_file}")
46
+ return yaml.safe_load(dep_file.read_text(encoding="utf-8"))
47
+
48
+
49
+ def detect_platform() -> str | None:
50
+ """Return 'macos', 'linux', or None for unsupported platforms."""
51
+ system = platform.system().lower()
52
+ if system == "darwin":
53
+ return "macos"
54
+ if system == "linux":
55
+ return "linux"
56
+ return None
57
+
58
+
59
+ def _get_platform(dep: dict[str, Any], platform_name: str | None) -> str | None:
60
+ """Return the install command for the given platform, if declared."""
61
+ platforms = dep.get("platforms")
62
+ if not platforms:
63
+ return None
64
+ return platforms.get(platform_name) if platform_name else None
65
+
66
+
67
+ def get_install_command(dep: dict[str, Any], platform_name: str | None) -> str | None:
68
+ """Build the best-effort install command for a dependency.
69
+
70
+ For skills/plugins without a platform map, fall back to a git clone command.
71
+ For CLIs, use the platform map. Returns None when no command can be derived.
72
+ """
73
+ typ = dep.get("type", "skill")
74
+ source = dep.get("source", "")
75
+ ref = dep.get("ref", "")
76
+
77
+ if typ in ("skill", "plugin"):
78
+ platform_cmd = _get_platform(dep, platform_name)
79
+ if platform_cmd:
80
+ return platform_cmd
81
+ if source and source != "inline":
82
+ target = Path.home() / ".claude" / "skills" / dep.get("_name", "unknown")
83
+ branch_arg = f"--branch {ref}" if ref else ""
84
+ return f"git clone {branch_arg} --single-branch --depth 1 {source} {target}"
85
+ return None
86
+
87
+ if typ == "cli":
88
+ return _get_platform(dep, platform_name)
89
+
90
+ return None
91
+
92
+
93
+ def check_clis(manifest: dict[str, Any], platform_name: str | None = None) -> list[dict[str, Any]]:
94
+ """Return a list of missing CLI dependencies with install instructions."""
95
+ missing: list[dict[str, Any]] = []
96
+ for name, cfg in manifest.get("skills", {}).items():
97
+ if cfg.get("type") != "cli":
98
+ continue
99
+ binary = cfg.get("binary", name)
100
+ if shutil.which(binary):
101
+ continue
102
+ missing.append(
103
+ {
104
+ "name": name,
105
+ "type": "cli",
106
+ "binary": binary,
107
+ "required": cfg.get("required", True),
108
+ "install_command": get_install_command(cfg, platform_name),
109
+ "install_note": cfg.get("install_note", ""),
110
+ "source": cfg.get("source", ""),
111
+ }
112
+ )
113
+ return missing
114
+
115
+
116
+ def check_skills(
117
+ manifest: dict[str, Any], project_root: Path, skills_dir: Path
118
+ ) -> list[dict[str, Any]]:
119
+ """Return a list of missing skill/plugin dependencies with install instructions."""
120
+ missing: list[dict[str, Any]] = []
121
+ for name, cfg in manifest.get("skills", {}).items():
122
+ typ = cfg.get("type", "skill")
123
+ if typ not in ("skill", "plugin"):
124
+ continue
125
+
126
+ source = cfg.get("source", "")
127
+ is_required = cfg.get("required", True)
128
+ default_install = cfg.get("default_install", is_required)
129
+
130
+ if source == "inline":
131
+ path = project_root / cfg.get("path", f".claude/skills/{name}")
132
+ expected = path / "SKILL.md"
133
+ else:
134
+ expected = skills_dir / name / "SKILL.md"
135
+
136
+ if expected.exists():
137
+ continue
138
+
139
+ cfg_with_name = dict(cfg)
140
+ cfg_with_name["_name"] = name
141
+ missing.append(
142
+ {
143
+ "name": name,
144
+ "type": typ,
145
+ "required": is_required,
146
+ "default_install": default_install,
147
+ "install_command": get_install_command(cfg_with_name, detect_platform()),
148
+ "install_note": cfg.get("install_note", ""),
149
+ "source": source,
150
+ "ref": cfg.get("ref", ""),
151
+ "expected_path": str(expected),
152
+ }
153
+ )
154
+ return missing
155
+
156
+
157
+ def _run_command(args: list[str], **kwargs: Any) -> subprocess.CompletedProcess[str]:
158
+ """Run a shell command and return the completed process."""
159
+ return subprocess.run(
160
+ args,
161
+ capture_output=True,
162
+ text=True,
163
+ check=False,
164
+ **kwargs,
165
+ )
166
+
167
+
168
+ def _resolve_remote_sha(target: Path, ref: str) -> str:
169
+ """Resolve a remote ref (branch or tag) to a commit SHA; empty on failure."""
170
+ proc = _run_command(["git", "-C", str(target), "ls-remote", "origin", ref])
171
+ if proc.returncode != 0 or not proc.stdout.strip():
172
+ return ""
173
+ return proc.stdout.split()[0]
174
+
175
+
176
+ def install_skill(
177
+ name: str,
178
+ source: str,
179
+ ref: str,
180
+ skills_dir: Path,
181
+ offline_cache: Path | None = None,
182
+ dry_run: bool = False,
183
+ ) -> dict[str, Any]:
184
+ """Install or update an external skill/plugin into `skills_dir/name`.
185
+
186
+ Returns a result dict with keys: name, installed, skipped, error.
187
+ """
188
+ target = skills_dir / name
189
+ result: dict[str, Any] = {"name": name, "installed": False, "skipped": False, "error": ""}
190
+
191
+ if target.exists():
192
+ # Check current ref and working tree state.
193
+ status_proc = _run_command(["git", "-C", str(target), "status", "--porcelain"])
194
+ if status_proc.stdout.strip():
195
+ result["error"] = f"Working tree of {target} is dirty; aborting."
196
+ return result
197
+
198
+ describe_proc = _run_command(
199
+ ["git", "-C", str(target), "describe", "--tags", "--exact-match"]
200
+ )
201
+ current_tag = describe_proc.stdout.strip() if describe_proc.returncode == 0 else ""
202
+ if current_tag == ref:
203
+ result["skipped"] = True
204
+ return result
205
+
206
+ rev_proc = _run_command(["git", "-C", str(target), "rev-parse", "HEAD"])
207
+ local_sha = rev_proc.stdout.strip() if rev_proc.returncode == 0 else ""
208
+ remote_sha = _resolve_remote_sha(target, ref)
209
+ if remote_sha and local_sha == remote_sha:
210
+ result["skipped"] = True
211
+ return result
212
+
213
+ current_ref = current_tag or local_sha
214
+ if dry_run:
215
+ result["error"] = f"Would update {name} from {current_ref} to {ref}."
216
+ return result
217
+
218
+ fetch_proc = _run_command(["git", "-C", str(target), "fetch", "origin", ref])
219
+ if fetch_proc.returncode != 0:
220
+ result["error"] = f"Failed to fetch {ref}: {fetch_proc.stderr.strip()}"
221
+ return result
222
+
223
+ checkout_proc = _run_command(["git", "-C", str(target), "checkout", "FETCH_HEAD"])
224
+ if checkout_proc.returncode != 0:
225
+ result["error"] = f"Failed to checkout {ref}: {checkout_proc.stderr.strip()}"
226
+ return result
227
+
228
+ result["installed"] = True
229
+ return result
230
+
231
+ # Fresh install.
232
+ if dry_run:
233
+ result["error"] = f"Would clone {name} at {ref}."
234
+ result["dry_run"] = True
235
+ return result
236
+
237
+ target.parent.mkdir(parents=True, exist_ok=True)
238
+
239
+ # Try offline cache first when provided.
240
+ if offline_cache:
241
+ cached = offline_cache / name
242
+ if cached.exists():
243
+ clone_proc = _run_command(
244
+ ["git", "clone", "--branch", ref, "--single-branch", "--depth", "1", str(cached), str(target)]
245
+ )
246
+ if clone_proc.returncode == 0:
247
+ result["installed"] = True
248
+ return result
249
+
250
+ branch_args = ["--branch", ref] if ref else []
251
+ args = ["git", "clone", *branch_args, "--single-branch", "--depth", "1", source, str(target)]
252
+ clone_proc = _run_command(args)
253
+ if clone_proc.returncode != 0:
254
+ result["error"] = f"Failed to clone {name}: {clone_proc.stderr.strip()}"
255
+ return result
256
+
257
+ result["installed"] = True
258
+ return result
259
+
260
+
261
+ def install_cli(name: str, command: str, dry_run: bool = False) -> dict[str, Any]:
262
+ """Install a CLI dependency using the platform-specific command.
263
+
264
+ Returns a result dict with keys: name, installed, dry_run, error.
265
+ """
266
+ result: dict[str, Any] = {"name": name, "installed": False, "dry_run": dry_run, "error": ""}
267
+
268
+ if dry_run:
269
+ return result
270
+
271
+ # Tokenize the command string, preserving quoted arguments.
272
+ args = shlex.split(command)
273
+ proc = _run_command(args)
274
+ if proc.returncode != 0:
275
+ result["error"] = proc.stderr.strip() or f"Command exited with {proc.returncode}"
276
+ return result
277
+
278
+ result["installed"] = True
279
+ return result
280
+
281
+
282
+ def init_openspec_config(
283
+ owner: str, name: str, branch: str, config_path: Path
284
+ ) -> dict[str, Any]:
285
+ """Write or update `.github/openspec-config.yml` when it still has placeholders.
286
+
287
+ Returns a result dict with keys: updated, skipped, error.
288
+ """
289
+ result: dict[str, Any] = {"updated": False, "skipped": False, "error": ""}
290
+
291
+ config_path.parent.mkdir(parents=True, exist_ok=True)
292
+
293
+ if config_path.exists():
294
+ try:
295
+ data = yaml.safe_load(config_path.read_text(encoding="utf-8")) or {}
296
+ except Exception as exc:
297
+ result["error"] = f"Failed to parse {config_path}: {exc}"
298
+ return result
299
+ else:
300
+ data = {}
301
+
302
+ repository = data.get("repository") or {}
303
+ current_owner = repository.get("owner", "")
304
+ current_name = repository.get("name", "")
305
+
306
+ if (
307
+ current_owner not in _PLACEHOLDER_OWNERS
308
+ and current_name not in _PLACEHOLDER_NAMES
309
+ and current_owner
310
+ and current_name
311
+ ):
312
+ result["skipped"] = True
313
+ return result
314
+
315
+ base = _DEFAULT_OPENSPEC_CONFIG.copy()
316
+ base["repository"] = {
317
+ "owner": owner,
318
+ "name": name,
319
+ "default_branch": branch,
320
+ }
321
+ # Merge with any existing non-repository sections to preserve user edits.
322
+ merged = {**base, **{k: v for k, v in data.items() if k != "repository"}}
323
+ merged["repository"] = base["repository"]
324
+
325
+ try:
326
+ config_path.write_text(
327
+ yaml.safe_dump(merged, allow_unicode=True, sort_keys=False),
328
+ encoding="utf-8",
329
+ )
330
+ except Exception as exc:
331
+ result["error"] = f"Failed to write {config_path}: {exc}"
332
+ return result
333
+
334
+ result["updated"] = True
335
+ return result
@@ -0,0 +1,175 @@
1
+ #!/usr/bin/env python3
2
+ """Maintain the per-package document index (documents.yaml).
3
+
4
+ The index lives in the issue-package root and summarizes, per document, which
5
+ stage produced it and whether it passed human confirmation, so agents can
6
+ understand package contents from a single file instead of scanning the tree.
7
+ It is regenerated from workflow-stage.yaml on every state mutation.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import argparse
13
+ import re
14
+ import sys
15
+ from datetime import datetime, timezone
16
+ from pathlib import Path
17
+ from typing import Any
18
+
19
+ import yaml
20
+
21
+ sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
22
+ from scripts.lincoln_paths import STATE_FILENAME
23
+
24
+ DOCUMENTS_FILENAME = "documents.yaml"
25
+
26
+ # Matches master-handoff-pm-to-ux-v{MAJOR}.{MINOR}.html under pages/docs/
27
+ HANDOFF_DOC_PATTERN = re.compile(r"pages/docs/master-handoff-pm-to-ux-v(\d+)\.(\d+)\.html$")
28
+ HANDOFF_YAML_PATTERN = re.compile(r"handoffs/pm-to-ux/pm-to-ux\.handoff\.yaml$")
29
+
30
+ # Matches prd-v{MAJOR}.{MINOR}.html under pages/docs/snapshots/
31
+ PRD_SNAPSHOT_PATTERN = re.compile(r"pages/docs/snapshots/prd-v(\d+)\.(\d+)\.html$")
32
+ # Matches current HTML PRD or legacy Markdown PRD paths
33
+ PRD_PATH_PATTERN = re.compile(r"^(pages/docs/prd\.html|prd\.md|requirements/.*/prd\.md)$")
34
+
35
+
36
+ def extract_markdown_version(path: Path) -> str | None:
37
+ """Look for `<!-- version: vX.Y -->` in a markdown file."""
38
+ if not path.exists():
39
+ return None
40
+ text = path.read_text(encoding="utf-8")
41
+ match = re.search(r"<!--\s*version:\s*(v\d+\.\d+)\s*-->", text, re.IGNORECASE)
42
+ return match.group(1) if match else None
43
+
44
+
45
+ def now_iso() -> str:
46
+ return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
47
+
48
+
49
+ def package_relative(path: str, process_slug: str) -> str:
50
+ """Strip the '<process_slug>/' prefix from a repo-relative artifact path."""
51
+ prefix = f"{process_slug}/"
52
+ return path[len(prefix):] if path.startswith(prefix) else path
53
+
54
+
55
+ def is_team_package_state(state_path: Path) -> bool:
56
+ """Only team issue packages (<slug>/workflow-stage.yaml) carry a document index."""
57
+ return state_path.name == STATE_FILENAME and state_path.parent.name != ".claude"
58
+
59
+
60
+ def _extract_document_version(rel_path: str, project_root: Path, process_slug: str) -> str | None:
61
+ """Detect version for PM→UX handoff documents and PRDs; returns None for others."""
62
+ full_path = project_root / process_slug / rel_path
63
+ if not full_path.exists():
64
+ return None
65
+
66
+ md_match = HANDOFF_DOC_PATTERN.search(rel_path)
67
+ if md_match:
68
+ return f"v{md_match.group(1)}.{md_match.group(2)}"
69
+
70
+ snapshot_match = PRD_SNAPSHOT_PATTERN.search(rel_path)
71
+ if snapshot_match:
72
+ return f"v{snapshot_match.group(1)}.{snapshot_match.group(2)}"
73
+
74
+ if PRD_PATH_PATTERN.search(rel_path):
75
+ return extract_markdown_version(full_path)
76
+
77
+ if HANDOFF_YAML_PATTERN.search(rel_path):
78
+ try:
79
+ data = yaml.safe_load(full_path.read_text(encoding="utf-8"))
80
+ if isinstance(data, dict):
81
+ return data.get("human_master_doc", {}).get("version")
82
+ except Exception:
83
+ pass
84
+
85
+ return None
86
+
87
+
88
+ def build_documents_index(
89
+ state: dict[str, Any],
90
+ process_slug: str,
91
+ generated_at: str | None = None,
92
+ project_root: Path | None = None,
93
+ ) -> dict[str, Any]:
94
+ """Build the index dict from state nodes; later/higher approvals win per path."""
95
+ current_run = state.get("current_run", {})
96
+ documents: dict[str, dict[str, Any]] = {}
97
+ root = project_root or Path(__file__).resolve().parents[1]
98
+ for node in state.get("nodes") or []:
99
+ stage_id = str(node.get("stage_id") or "")
100
+ for artifact in node.get("artifacts") or []:
101
+ rel = package_relative(str(artifact), process_slug)
102
+ entry = documents.setdefault(
103
+ rel,
104
+ {
105
+ "path": rel,
106
+ "stage": stage_id,
107
+ "status": "",
108
+ "gate_passed": False,
109
+ "human_confirmed": False,
110
+ "approved_by": None,
111
+ "version": None,
112
+ },
113
+ )
114
+ entry["stage"] = stage_id or entry["stage"]
115
+ entry["status"] = str(node.get("status") or "") or entry["status"]
116
+ version = _extract_document_version(rel, root, process_slug)
117
+ if version:
118
+ entry["version"] = version
119
+ if not node.get("gate_passed"):
120
+ continue
121
+ entry["gate_passed"] = True
122
+ approver = node.get("approved_by")
123
+ if approver and str(approver).startswith("human"):
124
+ entry["human_confirmed"] = True
125
+ entry["approved_by"] = approver
126
+ elif not entry["approved_by"]:
127
+ entry["approved_by"] = approver
128
+
129
+ return {
130
+ "process_slug": process_slug,
131
+ "issue_number": str(current_run.get("issue_number") or ""),
132
+ "current_stage": str(current_run.get("current_stage") or ""),
133
+ "last_updated": generated_at or now_iso(),
134
+ "documents": sorted(documents.values(), key=lambda d: d["path"]),
135
+ }
136
+
137
+
138
+ def write_documents_index(
139
+ state: dict[str, Any],
140
+ state_path: Path,
141
+ generated_at: str | None = None,
142
+ ) -> Path | None:
143
+ """Regenerate documents.yaml next to a team package state file; else return None."""
144
+ if not is_team_package_state(state_path):
145
+ return None
146
+ project_root = state_path.parents[1]
147
+ index = build_documents_index(state, state_path.parent.name, generated_at, project_root)
148
+ index_path = state_path.parent / DOCUMENTS_FILENAME
149
+ index_path.write_text(
150
+ yaml.dump(index, allow_unicode=True, sort_keys=False, width=120),
151
+ encoding="utf-8",
152
+ )
153
+ return index_path
154
+
155
+
156
+ def main() -> int:
157
+ parser = argparse.ArgumentParser(description="Regenerate the issue-package document index")
158
+ parser.add_argument("--state-file", required=True, help="Path to the package workflow-stage.yaml")
159
+ args = parser.parse_args()
160
+
161
+ state_path = Path(args.state_file).resolve()
162
+ if not state_path.is_file():
163
+ print(f"ERROR: state file not found: {state_path}", file=sys.stderr)
164
+ return 1
165
+ state = yaml.safe_load(state_path.read_text(encoding="utf-8"))
166
+ index_path = write_documents_index(state, state_path)
167
+ if index_path is None:
168
+ print(f"SKIP: {state_path} is not a team issue-package state file")
169
+ return 0
170
+ print(f"Updated {index_path}")
171
+ return 0
172
+
173
+
174
+ if __name__ == "__main__":
175
+ sys.exit(main())