@sushanglewis/lincoln 1.5.0 → 1.5.2

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 +1286 -916
  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,198 @@
1
+ #!/usr/bin/env python3
2
+ """Shared path helpers for Lincoln process packages."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import os
7
+ import re
8
+ import subprocess
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+ import yaml
13
+
14
+
15
+ PROJECT_ROOT = Path(__file__).resolve().parents[1]
16
+ LEGACY_STATE_PATH = PROJECT_ROOT / ".claude" / "workflow-state.yaml"
17
+ ROOT_STATE_PATH = PROJECT_ROOT / ".claude" / "workflow-stage.yaml"
18
+ STATE_FILENAME = "workflow-stage.yaml"
19
+ SOLO_STATE_DIR = ".context/workflow"
20
+ SOLO_PROCESS_SLUG = ".context"
21
+
22
+ RESERVED_PROCESS_DIRS = {
23
+ ".claude",
24
+ ".conductor",
25
+ ".context",
26
+ ".git",
27
+ ".github",
28
+ ".pytest_cache",
29
+ ".venv",
30
+ "docs",
31
+ "knowledge",
32
+ "node_modules",
33
+ "oss",
34
+ "products",
35
+ "scripts",
36
+ "tests",
37
+ "tools",
38
+ "venv",
39
+ }
40
+
41
+
42
+ def slugify(value: str) -> str:
43
+ slug = re.sub(r"[^a-zA-Z0-9]+", "-", value.strip()).strip("-").lower()
44
+ return slug or "lc-process"
45
+
46
+
47
+ def validate_slug(value: str) -> str:
48
+ slug = slugify(value)
49
+ if not re.fullmatch(r"[a-z0-9]+(?:-[a-z0-9]+)*", slug):
50
+ raise ValueError(f"Invalid process slug: {value}")
51
+ return slug
52
+
53
+
54
+ LC_NAME_RE = re.compile(r"[a-z0-9][a-z0-9-]*")
55
+
56
+
57
+ def validate_name(value: str, kind: str = "name") -> str:
58
+ """Validate a Lincoln identifier (workflow, role, skill, scenario key)."""
59
+ if not LC_NAME_RE.fullmatch(value):
60
+ raise ValueError(f"Invalid {kind}: {value!r}")
61
+ return value
62
+
63
+
64
+ def assert_contained(path: Path, parent: Path, desc: str = "path") -> Path:
65
+ """Raise ValueError if resolved path escapes parent directory."""
66
+ resolved = path.resolve()
67
+ base = parent.resolve()
68
+ if base not in resolved.parents and resolved != base:
69
+ raise ValueError(f"{desc} escapes allowed directory: {path}")
70
+ return resolved
71
+
72
+
73
+ def atomic_write_text(path: Path, content: str, encoding: str = "utf-8") -> None:
74
+ """Atomically write text to path using a temporary file and rename."""
75
+ tmp = path.with_suffix(path.suffix + ".tmp")
76
+ try:
77
+ tmp.write_text(content, encoding=encoding)
78
+ tmp.replace(path)
79
+ except Exception:
80
+ if tmp.exists():
81
+ tmp.unlink()
82
+ raise
83
+
84
+
85
+ def branch_slug(project_root: Path = PROJECT_ROOT) -> str:
86
+ try:
87
+ branch = subprocess.check_output(
88
+ ["git", "branch", "--show-current"],
89
+ cwd=project_root,
90
+ text=True,
91
+ stderr=subprocess.DEVNULL,
92
+ ).strip()
93
+ except Exception:
94
+ branch = ""
95
+ if branch.startswith("lincoln/"):
96
+ branch = branch.split("/", 1)[1]
97
+ return slugify(branch or project_root.name)
98
+
99
+
100
+ def default_process_slug(project_root: Path = PROJECT_ROOT) -> str:
101
+ """Return a sensible default process package slug for a Conductor workspace."""
102
+ env_slug = os.environ.get("LINCOLN_PROCESS_SLUG") or os.environ.get("CONDUCTOR_WORKSPACE_NAME")
103
+ if env_slug:
104
+ return validate_slug(env_slug)
105
+
106
+ # Conductor paths commonly look like .../workspaces/<workspace-slug>/<repo-name>.
107
+ if project_root.parent.name and project_root.parent.parent.name == "workspaces":
108
+ return validate_slug(project_root.parent.name)
109
+
110
+ return validate_slug(branch_slug(project_root))
111
+
112
+
113
+ def discover_process_state_paths(project_root: Path = PROJECT_ROOT) -> list[Path]:
114
+ """Find branch process package state files at <process_slug>/workflow-stage.yaml."""
115
+ paths: list[Path] = []
116
+ for candidate in project_root.iterdir():
117
+ if not candidate.is_dir() or candidate.name in RESERVED_PROCESS_DIRS:
118
+ continue
119
+ state_path = candidate / STATE_FILENAME
120
+ if state_path.is_file():
121
+ paths.append(state_path)
122
+ return sorted(paths, key=lambda p: p.stat().st_mtime, reverse=True)
123
+
124
+
125
+ def discover_solo_state_paths(project_root: Path = PROJECT_ROOT) -> list[Path]:
126
+ """Find session-scoped solo workflow instances under .context/workflow/."""
127
+ solo_dir = project_root / SOLO_STATE_DIR
128
+ if not solo_dir.is_dir():
129
+ return []
130
+ paths = [p for p in solo_dir.glob("*.yaml") if p.is_file()]
131
+ return sorted(paths, key=lambda p: p.stat().st_mtime, reverse=True)
132
+
133
+
134
+ def resolve_state_path(path: Path | None = None, project_root: Path = PROJECT_ROOT) -> Path:
135
+ """Resolve canonical process state, falling back to legacy root state files."""
136
+ env_path = os.environ.get("LINCOLN_STATE_FILE")
137
+ if path is None and env_path:
138
+ path = Path(env_path)
139
+
140
+ if path is not None:
141
+ path = path if path.is_absolute() else project_root / path
142
+ if path.exists():
143
+ return path
144
+ if path not in (ROOT_STATE_PATH, LEGACY_STATE_PATH):
145
+ return path
146
+
147
+ discovered = discover_process_state_paths(project_root)
148
+ if discovered:
149
+ return discovered[0]
150
+ solo = discover_solo_state_paths(project_root)
151
+ if solo:
152
+ return solo[0]
153
+ if ROOT_STATE_PATH.exists():
154
+ return ROOT_STATE_PATH
155
+ if LEGACY_STATE_PATH.exists():
156
+ return LEGACY_STATE_PATH
157
+ return ROOT_STATE_PATH
158
+
159
+
160
+ def load_yaml(path: Path) -> Any:
161
+ return yaml.safe_load(path.read_text(encoding="utf-8"))
162
+
163
+
164
+ def get_process_slug(state: dict[str, Any] | None = None, state_path: Path | None = None) -> str:
165
+ if state:
166
+ variables = state.get("current_run", {}).get("variables", {})
167
+ if variables.get("process_slug"):
168
+ raw = str(variables["process_slug"])
169
+ if raw == SOLO_PROCESS_SLUG:
170
+ return raw
171
+ return validate_slug(raw)
172
+ if state_path and state_path.name == STATE_FILENAME and state_path.parent != PROJECT_ROOT / ".claude":
173
+ return validate_slug(state_path.parent.name)
174
+ return default_process_slug(PROJECT_ROOT)
175
+
176
+
177
+ def interpolate_process_path(path: str, state: dict[str, Any] | None = None, state_path: Path | None = None) -> str:
178
+ return path.replace("{process_slug}", get_process_slug(state, state_path))
179
+
180
+
181
+ def process_package_root(
182
+ process_slug: str | None = None,
183
+ state: dict[str, Any] | None = None,
184
+ state_path: Path | None = None,
185
+ project_root: Path = PROJECT_ROOT,
186
+ ) -> Path:
187
+ slug = process_slug or get_process_slug(state, state_path)
188
+ if slug == SOLO_PROCESS_SLUG:
189
+ return project_root / SOLO_PROCESS_SLUG
190
+ return project_root / validate_slug(slug)
191
+
192
+
193
+ def is_process_state_path(path: Path, project_root: Path = PROJECT_ROOT) -> bool:
194
+ try:
195
+ rel = path.resolve().relative_to(project_root.resolve())
196
+ except ValueError:
197
+ return False
198
+ return len(rel.parts) == 2 and rel.parts[1] == STATE_FILENAME and rel.parts[0] not in RESERVED_PROCESS_DIRS
@@ -0,0 +1,227 @@
1
+ #!/usr/bin/env python3
2
+ """PRD lifecycle helper: freeze snapshots, read current version, migrate legacy paths."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import os
8
+ import re
9
+ import sys
10
+ from pathlib import Path
11
+
12
+ PROJECT_ROOT = Path(__file__).resolve().parents[1]
13
+ if str(PROJECT_ROOT) not in sys.path:
14
+ sys.path.insert(0, str(PROJECT_ROOT))
15
+
16
+ from scripts.lincoln_index import VERSION_COMMENT_RE
17
+ from scripts.lincoln_paths import (
18
+ atomic_write_text,
19
+ get_process_slug,
20
+ load_yaml,
21
+ process_package_root,
22
+ resolve_state_path,
23
+ )
24
+
25
+
26
+ def extract_prd_version(prd_path: Path) -> str | None:
27
+ """Return the version marker from an HTML PRD or a legacy Markdown PRD."""
28
+ if not prd_path.exists():
29
+ return None
30
+ text = prd_path.read_text(encoding="utf-8")
31
+ match = VERSION_COMMENT_RE.search(text)
32
+ return match.group(1) if match else None
33
+
34
+
35
+ # Backwards-compatible alias used by early tests.
36
+ extract_version = extract_prd_version
37
+
38
+ VERSION_MARKER_RE = re.compile(r"\n?\s*\n?\Z", re.MULTILINE)
39
+
40
+
41
+ def _error(message: str) -> None:
42
+ raise RuntimeError(message)
43
+
44
+
45
+ def _package_root(args: argparse.Namespace) -> Path:
46
+ if args.package:
47
+ return PROJECT_ROOT / args.package
48
+
49
+ env_slug = os.environ.get("LINCOLN_PROCESS_SLUG")
50
+ if env_slug:
51
+ root = PROJECT_ROOT / env_slug
52
+ if root.exists():
53
+ return root
54
+ cwd_root = Path.cwd() / env_slug
55
+ if cwd_root.exists():
56
+ return cwd_root
57
+ return cwd_root
58
+
59
+ state_path = resolve_state_path(None, PROJECT_ROOT)
60
+ if state_path is not None and state_path.parent.exists():
61
+ try:
62
+ if state_path.exists():
63
+ state = load_yaml(state_path)
64
+ return process_package_root(
65
+ state=state, state_path=state_path, project_root=PROJECT_ROOT
66
+ )
67
+ return state_path.parent
68
+ except Exception:
69
+ pass
70
+
71
+ _error(
72
+ "Could not determine package root. "
73
+ "Use --package or run from a Lincoln project with a state file."
74
+ )
75
+
76
+
77
+ def _prd_path(root: Path) -> Path:
78
+ """Return the primary HTML PRD path, falling back to legacy root prd.md."""
79
+ html_path = root / "pages" / "docs" / "prd.html"
80
+ if html_path.exists():
81
+ return html_path
82
+ legacy_path = root / "prd.md"
83
+ if legacy_path.exists():
84
+ return legacy_path
85
+ return html_path
86
+
87
+
88
+ def _snapshot_path(prd_path: Path, version: str) -> Path:
89
+ """Return the immutable snapshot path next to the PRD source."""
90
+ if prd_path.name == "prd.html":
91
+ return prd_path.parent / "snapshots" / f"prd-{version}.html"
92
+ return prd_path.with_name(f"prd-{version}.md")
93
+
94
+
95
+ def freeze(package_root: Path | None = None, *, args: argparse.Namespace | None = None) -> Path:
96
+ """Copy the PRD to an immutable versioned snapshot based on its version marker."""
97
+ root = package_root or (args and _package_root(args))
98
+ if not root:
99
+ _error("package_root is required")
100
+ prd_path = _prd_path(root)
101
+ if not prd_path.exists():
102
+ _error(f"PRD not found: {prd_path}")
103
+
104
+ version = extract_prd_version(prd_path)
105
+ if not version:
106
+ _error(f"No version marker found in {prd_path}; add '<!-- version: vX.Y -->' before freezing.")
107
+
108
+ snapshot_path = _snapshot_path(prd_path, version)
109
+ snapshot_path.parent.mkdir(parents=True, exist_ok=True)
110
+ if snapshot_path.exists():
111
+ _error(
112
+ f"Snapshot already exists: {snapshot_path}. "
113
+ "PRD snapshots are protected by an immutability guarantee. "
114
+ "Bump the version marker in the PRD and freeze again."
115
+ )
116
+
117
+ atomic_write_text(snapshot_path, prd_path.read_text(encoding="utf-8"))
118
+ print(f"Frozen {snapshot_path}")
119
+ return snapshot_path
120
+
121
+
122
+ def current_version(package_root: Path | None = None, *, args: argparse.Namespace | None = None) -> str:
123
+ """Print the version marker from the PRD."""
124
+ root = package_root or (args and _package_root(args))
125
+ if not root:
126
+ _error("package_root is required")
127
+ prd_path = _prd_path(root)
128
+ if not prd_path.exists():
129
+ _error(f"PRD not found: {prd_path}")
130
+
131
+ version = extract_prd_version(prd_path)
132
+ if not version:
133
+ _error(f"No version marker found in {prd_path}.")
134
+
135
+ print(version)
136
+ return version
137
+
138
+
139
+ def _render_prd_html(title: str, version: str, markdown: str) -> str:
140
+ """Wrap legacy Markdown PRD content in the page-doc HTML template."""
141
+ template_path = PROJECT_ROOT / ".claude" / "templates" / "issue-package" / "page-doc.html.tpl"
142
+ if not template_path.exists():
143
+ return markdown
144
+ template = template_path.read_text(encoding="utf-8")
145
+ replacements = {
146
+ "{TITLE}": title,
147
+ "{NAV_GROUP}": "Docs",
148
+ "{VERSION}": version,
149
+ "{STAGE}": "clarify",
150
+ "{UID}": "",
151
+ "{MARKDOWN_SOURCE}": markdown,
152
+ }
153
+ for placeholder, value in replacements.items():
154
+ template = template.replace(placeholder, value)
155
+ return template
156
+
157
+
158
+ def migrate(
159
+ package_root: Path | None = None,
160
+ session_id: str | None = None,
161
+ *,
162
+ args: argparse.Namespace | None = None,
163
+ ) -> Path:
164
+ """Move a legacy requirements/{session_id}/prd.md into the HTML portal."""
165
+ root = package_root or (args and _package_root(args))
166
+ if not root:
167
+ _error("package_root is required")
168
+
169
+ sid = session_id or (args and args.session_id)
170
+ if not sid:
171
+ _error("--session-id is required for migrate")
172
+
173
+ legacy_path = root / "requirements" / sid / "prd.md"
174
+ if not legacy_path.exists():
175
+ _error(f"Legacy PRD not found: {legacy_path}")
176
+
177
+ target_path = root / "pages" / "docs" / "prd.html"
178
+ if target_path.exists():
179
+ _error(f"Root PRD already exists: {target_path}. Manual merge required.")
180
+
181
+ content = legacy_path.read_text(encoding="utf-8")
182
+ version = extract_prd_version(legacy_path)
183
+ if not version:
184
+ version = "v1.0"
185
+ content = f"<!-- version: {version} -->\n\n" + content.lstrip("\n")
186
+ else:
187
+ # Avoid duplicating the version marker once the template injects it.
188
+ content = VERSION_COMMENT_RE.sub("", content, count=1).strip("\n")
189
+
190
+ html = _render_prd_html("PRD", version, content)
191
+
192
+ target_path.parent.mkdir(parents=True, exist_ok=True)
193
+ atomic_write_text(target_path, html)
194
+ legacy_path.unlink()
195
+ print(f"Migrated {legacy_path} -> {target_path}")
196
+ return target_path
197
+
198
+
199
+ def main() -> int:
200
+ parser = argparse.ArgumentParser(description="Lincoln PRD lifecycle helper")
201
+ parser.add_argument("--package", help="Process package slug (e.g., issue-85)")
202
+ parser.add_argument("--session-id", help="Interview session id (required for migrate)")
203
+ subparsers = parser.add_subparsers(dest="action", required=True)
204
+
205
+ subparsers.add_parser("freeze", help="Freeze pages/docs/prd.html into an immutable versioned snapshot")
206
+ subparsers.add_parser("current-version", help="Print the current PRD version marker")
207
+ migrate_parser = subparsers.add_parser("migrate", help="Move legacy requirements/{session_id}/prd.md into pages/docs/prd.html")
208
+ migrate_parser.add_argument("--session-id", required=True, help="Legacy session directory name")
209
+
210
+ args = parser.parse_args()
211
+
212
+ try:
213
+ if args.action == "freeze":
214
+ freeze(args=args)
215
+ elif args.action == "current-version":
216
+ current_version(args=args)
217
+ elif args.action == "migrate":
218
+ migrate(args=args)
219
+ except RuntimeError as exc:
220
+ print(f"ERROR: {exc}", file=sys.stderr)
221
+ sys.exit(1)
222
+
223
+ sys.exit(0)
224
+
225
+
226
+ if __name__ == "__main__":
227
+ main()
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env python3
2
+ """Print the content of a Lincoln agent role.
3
+
4
+ Usage:
5
+ python3 scripts/lincoln_role.py --role pm [topic words...]
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import argparse
11
+ import sys
12
+ from pathlib import Path
13
+
14
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
15
+
16
+ from lincoln_paths import PROJECT_ROOT, assert_contained, validate_name # noqa: E402
17
+
18
+ AGENTS_DIR = PROJECT_ROOT / ".claude" / "agents"
19
+
20
+
21
+ def main() -> int:
22
+ parser = argparse.ArgumentParser(description="Print a Lincoln agent role")
23
+ parser.add_argument("--role", required=True, help="Agent role filename stem")
24
+ parser.add_argument("topic", nargs="*", help="Optional topic/context")
25
+ args = parser.parse_args()
26
+
27
+ try:
28
+ validate_name(args.role, "role name")
29
+ path = AGENTS_DIR / f"{args.role}.md"
30
+ assert_contained(path, AGENTS_DIR, "agent role path")
31
+ if not path.exists():
32
+ print(f"ERROR: agent role not found: {path}", file=sys.stderr)
33
+ return 1
34
+
35
+ text = path.read_text(encoding="utf-8")
36
+ if args.topic:
37
+ print(f"Topic/context: {' '.join(args.topic)}\n")
38
+ print(text)
39
+ return 0
40
+ except ValueError as exc:
41
+ print(f"ERROR: {exc}", file=sys.stderr)
42
+ return 1
43
+
44
+
45
+ if __name__ == "__main__":
46
+ sys.exit(main())
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env python3
2
+ """Print the prompt for a Lincoln scenario.
3
+
4
+ Usage:
5
+ python3 scripts/lincoln_scenario.py --scenario make-prd [topic words...]
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import argparse
11
+ import sys
12
+ from pathlib import Path
13
+
14
+ import yaml
15
+
16
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
17
+
18
+ from lincoln_paths import PROJECT_ROOT, validate_name # noqa: E402
19
+
20
+ SCENARIOS_PATH = PROJECT_ROOT / ".claude" / "harnesses" / "scenarios.yaml"
21
+
22
+
23
+ def main() -> int:
24
+ parser = argparse.ArgumentParser(description="Print a Lincoln scenario prompt")
25
+ parser.add_argument("--scenario", required=True, help="Scenario id")
26
+ parser.add_argument("topic", nargs="*", help="Optional topic/context")
27
+ args = parser.parse_args()
28
+
29
+ if not SCENARIOS_PATH.exists():
30
+ print(f"ERROR: scenarios file not found: {SCENARIOS_PATH}", file=sys.stderr)
31
+ return 1
32
+
33
+ try:
34
+ validate_name(args.scenario, "scenario id")
35
+ except ValueError as exc:
36
+ print(f"ERROR: {exc}", file=sys.stderr)
37
+ return 1
38
+
39
+ data = yaml.safe_load(SCENARIOS_PATH.read_text(encoding="utf-8")) or {}
40
+ scenarios = data.get("scenarios", {})
41
+ scenario = scenarios.get(args.scenario)
42
+ if not scenario:
43
+ available = ", ".join(sorted(scenarios))
44
+ print(f"ERROR: scenario '{args.scenario}' not found. Available: {available}", file=sys.stderr)
45
+ return 1
46
+
47
+ if args.topic:
48
+ print(f"Topic/context: {' '.join(args.topic)}\n")
49
+ print(f"# Scenario: {args.scenario}")
50
+ print(f"\nDescription: {scenario.get('description', '')}")
51
+ print(f"Recommended role: {scenario.get('role', '')}")
52
+ print(f"Skills: {', '.join(scenario.get('skills', []))}")
53
+ print(f"\nGoal: {scenario.get('goal', '')}")
54
+ print("\nExecute the recommended skills in order. Follow Lincoln stage gates and human_gate rules.")
55
+ return 0
56
+
57
+
58
+ if __name__ == "__main__":
59
+ sys.exit(main())
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env python3
2
+ """Estimate the token cost of a Lincoln session-start hook output (#63).
3
+
4
+ Tries tiktoken for an accurate count; falls back to a stable heuristic
5
+ (CJK characters as one token each, whitespace-separated non-CJK words as
6
+ tokens) so the metric stays deterministic without requiring an extra dependency.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import argparse
12
+ import json
13
+ import re
14
+ import sys
15
+ from pathlib import Path
16
+ from typing import Any
17
+
18
+ CJK_RE = re.compile(
19
+ r"[一-鿿぀-ゟ゠-ヿ가-힯]"
20
+ )
21
+
22
+
23
+ def estimate_tokens(text: str) -> int:
24
+ """Return a deterministic token estimate for *text*."""
25
+ try:
26
+ import tiktoken # type: ignore
27
+
28
+ enc = tiktoken.get_encoding("cl100k_base")
29
+ return len(enc.encode(text))
30
+ except Exception:
31
+ pass
32
+
33
+ cjk_tokens = len(CJK_RE.findall(text))
34
+ non_cjk = CJK_RE.sub(" ", text)
35
+ word_tokens = len(non_cjk.split())
36
+ return cjk_tokens + word_tokens
37
+
38
+
39
+ def compute_metrics(text: str, stage: str = "", status: str = "") -> dict[str, Any]:
40
+ return {
41
+ "schema_version": "1.0.0",
42
+ "stage": stage,
43
+ "status": status,
44
+ "bytes": len(text.encode("utf-8")),
45
+ "chars": len(text),
46
+ "tokens": estimate_tokens(text),
47
+ }
48
+
49
+
50
+ def main(argv: list[str] | None = None) -> int:
51
+ parser = argparse.ArgumentParser(
52
+ description="Estimate token count for a Lincoln session-start output."
53
+ )
54
+ parser.add_argument("--input", type=Path, required=True, help="File to analyze.")
55
+ parser.add_argument("--output", type=Path, required=True, help="JSON output path.")
56
+ parser.add_argument("--stage", default="", help="Current stage id.")
57
+ parser.add_argument("--status", default="", help="Current stage status.")
58
+ args = parser.parse_args(argv)
59
+
60
+ text = args.input.read_text(encoding="utf-8")
61
+ metrics = compute_metrics(text, stage=args.stage, status=args.status)
62
+ args.output.parent.mkdir(parents=True, exist_ok=True)
63
+ args.output.write_text(
64
+ json.dumps(metrics, ensure_ascii=False, indent=2) + "\n",
65
+ encoding="utf-8",
66
+ )
67
+ return 0
68
+
69
+
70
+ if __name__ == "__main__":
71
+ sys.exit(main())
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env python3
2
+ """Print the content of a Lincoln skill.
3
+
4
+ Usage:
5
+ python3 scripts/lincoln_skill_prompt.py --skill lc-first-principles
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import argparse
11
+ import sys
12
+ from pathlib import Path
13
+
14
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
15
+
16
+ from lincoln_paths import PROJECT_ROOT, assert_contained, validate_name # noqa: E402
17
+
18
+ SKILLS_DIR = PROJECT_ROOT / ".claude" / "skills"
19
+
20
+
21
+ def main() -> int:
22
+ parser = argparse.ArgumentParser(description="Print a Lincoln skill prompt")
23
+ parser.add_argument("--skill", required=True, help="Skill folder name")
24
+ args = parser.parse_args()
25
+
26
+ try:
27
+ validate_name(args.skill, "skill name")
28
+ skill_dir = SKILLS_DIR / args.skill
29
+ skill_md = skill_dir / "SKILL.md"
30
+ assert_contained(skill_md, SKILLS_DIR, "skill path")
31
+ if not skill_md.exists():
32
+ print(f"ERROR: skill not found: {skill_dir}", file=sys.stderr)
33
+ return 1
34
+
35
+ parts = [skill_md.read_text(encoding="utf-8")]
36
+ prompt_path = skill_dir / "prompts" / "main.md"
37
+ if prompt_path.exists():
38
+ parts.append(prompt_path.read_text(encoding="utf-8"))
39
+
40
+ print("\n\n".join(parts))
41
+ return 0
42
+ except ValueError as exc:
43
+ print(f"ERROR: {exc}", file=sys.stderr)
44
+ return 1
45
+
46
+
47
+ if __name__ == "__main__":
48
+ sys.exit(main())