@sushanglewis/lincoln 1.5.0 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.d.ts +3 -0
- package/dist/main.js +1231 -906
- package/dist/main.js.map +1 -1
- package/dist/postinstall.d.ts +3 -0
- package/dist/postinstall.js +18 -0
- package/dist/postinstall.js.map +1 -0
- package/framework/.claude/agents/_contract.md +79 -0
- package/framework/.claude/agents/architect.md +31 -0
- package/framework/.claude/agents/default.md +151 -0
- package/framework/.claude/agents/designer.md +33 -0
- package/framework/.claude/agents/engineer.md +42 -0
- package/framework/.claude/agents/external/NOTICES.md +12 -0
- package/framework/.claude/agents/external/everything-claude-code.manifest.yaml +23 -0
- package/framework/.claude/agents/external/oh-my-claudecode.manifest.yaml +26 -0
- package/framework/.claude/agents/external/wshobson-agents.manifest.yaml +23 -0
- package/framework/.claude/agents/frontend-engineer.md +32 -0
- package/framework/.claude/agents/knowledge-curator.md +28 -0
- package/framework/.claude/agents/pm.md +38 -0
- package/framework/.claude/agents/qa.md +28 -0
- package/framework/.claude/agents/release-coordinator.md +25 -0
- package/framework/.claude/agents/researcher.md +41 -0
- package/framework/.claude/harnesses/claude-code.yaml +15 -0
- package/framework/.claude/harnesses/codex.yaml +40 -0
- package/framework/.claude/harnesses/command-map.yaml +255 -0
- package/framework/.claude/harnesses/opencode.yaml +32 -0
- package/framework/.claude/harnesses/scenarios.yaml +45 -0
- package/framework/.claude/hooks/on-pr-event.sh +69 -0
- package/framework/.claude/hooks/on-session-start.sh +427 -0
- package/framework/.claude/hooks/on-stop.sh +104 -0
- package/framework/.claude/hooks/post-tool-use.sh +161 -0
- package/framework/.claude/hooks/pre-tool-use.sh +246 -0
- package/framework/.claude/schemas/agent-definition.schema.json +35 -0
- package/framework/.claude/schemas/benchmark-report.schema.json +148 -0
- package/framework/.claude/schemas/pm-to-ux-handoff.schema.yaml +128 -0
- package/framework/.claude/schemas/stage-definition.schema.json +233 -0
- package/framework/.claude/schemas/workflow-stage.schema.json +86 -0
- package/framework/.claude/schemas/workflow-template.schema.json +88 -0
- package/framework/.claude/settings.json +14 -0
- package/framework/.claude/skills/build-product-prototype/SKILL.md +35 -0
- package/framework/.claude/skills/build-product-prototype/prompts/main.md +78 -0
- package/framework/.claude/skills/clarify-requirements/SKILL.md +32 -0
- package/framework/.claude/skills/clarify-requirements/prompts/main.md +81 -0
- package/framework/.claude/skills/dependencies.yaml +139 -0
- package/framework/.claude/skills/draft-product-design/SKILL.md +40 -0
- package/framework/.claude/skills/draft-product-design/prompts/main.md +47 -0
- package/framework/.claude/skills/lc-analyze-frameworks/SKILL.md +43 -0
- package/framework/.claude/skills/lc-analyze-frameworks/prompts/main.md +95 -0
- package/framework/.claude/skills/lc-benchmark/SKILL.md +61 -0
- package/framework/.claude/skills/lc-build-codebase-knowledge/SKILL.md +38 -0
- package/framework/.claude/skills/lc-build-codebase-knowledge/prompts/build-codebase-knowledge.md +61 -0
- package/framework/.claude/skills/lc-collect-intelligence/SKILL.md +43 -0
- package/framework/.claude/skills/lc-collect-intelligence/prompts/main.md +72 -0
- package/framework/.claude/skills/lc-competitive-analysis/SKILL.md +43 -0
- package/framework/.claude/skills/lc-competitive-analysis/prompts/main.md +70 -0
- package/framework/.claude/skills/lc-explore-opensource/SKILL.md +36 -0
- package/framework/.claude/skills/lc-explore-opensource/prompts/explore-opensource.md +77 -0
- package/framework/.claude/skills/lc-first-principles/SKILL.md +43 -0
- package/framework/.claude/skills/lc-first-principles/prompts/main.md +73 -0
- package/framework/.claude/skills/lc-handoff/SKILL.md +34 -0
- package/framework/.claude/skills/lc-init-branch/SKILL.md +52 -0
- package/framework/.claude/skills/lc-market-research/SKILL.md +43 -0
- package/framework/.claude/skills/lc-market-research/prompts/main.md +79 -0
- package/framework/.claude/skills/lc-product-research/SKILL.md +43 -0
- package/framework/.claude/skills/lc-product-research/prompts/main.md +80 -0
- package/framework/.claude/skills/lc-research-report/SKILL.md +43 -0
- package/framework/.claude/skills/lc-research-report/prompts/main.md +90 -0
- package/framework/.claude/skills/lc-research-scope/SKILL.md +43 -0
- package/framework/.claude/skills/lc-research-scope/prompts/main.md +82 -0
- package/framework/.claude/skills/lc-setup/SKILL.md +68 -0
- package/framework/.claude/skills/lc-setup/prompts/main.md +88 -0
- package/framework/.claude/skills/lc-stage/SKILL.md +45 -0
- package/framework/.claude/skills/lc-stakeholder-research/SKILL.md +43 -0
- package/framework/.claude/skills/lc-stakeholder-research/prompts/main.md +65 -0
- package/framework/.claude/skills/lc-status/SKILL.md +25 -0
- package/framework/.claude/skills/lc-storytelling/SKILL.md +43 -0
- package/framework/.claude/skills/lc-storytelling/prompts/main.md +77 -0
- package/framework/.claude/skills/lc-wf/SKILL.md +54 -0
- package/framework/.claude/skills/lc-workflow/SKILL.md +48 -0
- package/framework/.claude/skills/lc-workflow/validators/validate.py +755 -0
- package/framework/.claude/skills/lc-workflow-router/SKILL.md +41 -0
- package/framework/.claude/skills/lc-workflow-router/prompts/intake-prompt.md +91 -0
- package/framework/.claude/skills/lc-workflow-router/prompts/router-prompt.md +44 -0
- package/framework/.claude/skills/plan-tdd-development/SKILL.md +31 -0
- package/framework/.claude/skills/plan-tdd-development/prompts/main.md +44 -0
- package/framework/.claude/skills/process-interview/SKILL.md +32 -0
- package/framework/.claude/skills/process-interview/prompts/main.md +47 -0
- package/framework/.claude/skills/propose-with-openspec/SKILL.md +37 -0
- package/framework/.claude/skills/propose-with-openspec/prompts/main.md +53 -0
- package/framework/.claude/skills/split-to-github/SKILL.md +32 -0
- package/framework/.claude/skills/split-to-github/prompts/main.md +42 -0
- package/framework/.claude/skills/sync-to-knowledge/SKILL.md +35 -0
- package/framework/.claude/skills/sync-to-knowledge/prompts/main.md +54 -0
- package/framework/.claude/skills/workflow-continue/SKILL.md +24 -0
- package/framework/.claude/skills/workflow-continue/prompts/main.md +25 -0
- package/framework/.claude/stages/build-codebase-knowledge.yaml +42 -0
- package/framework/.claude/stages/clarify.yaml +59 -0
- package/framework/.claude/stages/explore-opensource.yaml +40 -0
- package/framework/.claude/stages/implement.yaml +60 -0
- package/framework/.claude/stages/ingest.yaml +40 -0
- package/framework/.claude/stages/lc-analyze-frameworks.yaml +37 -0
- package/framework/.claude/stages/lc-collect-intelligence.yaml +37 -0
- package/framework/.claude/stages/lc-competitive-analysis.yaml +37 -0
- package/framework/.claude/stages/lc-first-principles.yaml +37 -0
- package/framework/.claude/stages/lc-market-research.yaml +37 -0
- package/framework/.claude/stages/lc-product-research.yaml +37 -0
- package/framework/.claude/stages/lc-research-report.yaml +38 -0
- package/framework/.claude/stages/lc-research-scope.yaml +38 -0
- package/framework/.claude/stages/lc-stakeholder-research.yaml +37 -0
- package/framework/.claude/stages/lc-storytelling.yaml +37 -0
- package/framework/.claude/stages/product-design-docs.yaml +66 -0
- package/framework/.claude/stages/product-prototype.yaml +53 -0
- package/framework/.claude/stages/propose.yaml +45 -0
- package/framework/.claude/stages/split.yaml +41 -0
- package/framework/.claude/stages/sync-knowledge.yaml +49 -0
- package/framework/.claude/stages/tdd-development-plan.yaml +43 -0
- package/framework/.claude/stages/workflow-router.yaml +45 -0
- package/framework/.claude/templates/solo-workflow-context.yaml +23 -0
- package/framework/.claude/workflows/README.md +93 -0
- package/framework/.claude/workflows/bug-fix.yaml +81 -0
- package/framework/.claude/workflows/design-spike.yaml +55 -0
- package/framework/.claude/workflows/existing-project-iteration.yaml +106 -0
- package/framework/.claude/workflows/interview-to-knowledge.yaml +189 -0
- package/framework/.claude/workflows/oss-first-design.yaml +100 -0
- package/framework/.claude/workflows/pm-research.yaml +115 -0
- package/framework/.claude-plugin/hooks.json +6 -0
- package/framework/.claude-plugin/marketplace.json +32 -0
- package/framework/.claude-plugin/plugin.json +57 -0
- package/framework/.version-bump.json +43 -0
- package/framework/CLAUDE.md +140 -0
- package/framework/CONTRIBUTING.md +219 -0
- package/framework/LICENSE +21 -0
- package/framework/README.en.md +139 -0
- package/framework/README.md +139 -0
- package/framework/RELEASE.md +334 -0
- package/framework/SKILL.md +24 -0
- package/framework/USAGE.md +356 -0
- package/framework/manifest.json +1214 -0
- package/framework/requirements.txt +4 -0
- package/framework/scripts/__init__.py +0 -0
- package/framework/scripts/build_codebase_knowledge.py +76 -0
- package/framework/scripts/bump_version.py +181 -0
- package/framework/scripts/check-dependency-drift.py +69 -0
- package/framework/scripts/check-harness-drift.sh +57 -0
- package/framework/scripts/check-main-merge-hygiene.py +128 -0
- package/framework/scripts/check-skill-dependencies.sh +124 -0
- package/framework/scripts/init-lincoln-branch.sh +300 -0
- package/framework/scripts/init-project.sh +184 -0
- package/framework/scripts/lc-benchmark-cli.py +66 -0
- package/framework/scripts/lincoln-audit.py +288 -0
- package/framework/scripts/lincoln-setup.py +651 -0
- package/framework/scripts/lincoln-status.py +448 -0
- package/framework/scripts/lincoln_benchmark.py +224 -0
- package/framework/scripts/lincoln_benchmark_eval.py +104 -0
- package/framework/scripts/lincoln_benchmark_metrics.py +818 -0
- package/framework/scripts/lincoln_benchmark_report.py +141 -0
- package/framework/scripts/lincoln_command_map.py +289 -0
- package/framework/scripts/lincoln_dependency_manager.py +335 -0
- package/framework/scripts/lincoln_documents.py +175 -0
- package/framework/scripts/lincoln_harness_adapter.py +319 -0
- package/framework/scripts/lincoln_index.py +255 -0
- package/framework/scripts/lincoln_paths.py +198 -0
- package/framework/scripts/lincoln_prd.py +227 -0
- package/framework/scripts/lincoln_role.py +46 -0
- package/framework/scripts/lincoln_scenario.py +59 -0
- package/framework/scripts/lincoln_session_start_metrics.py +71 -0
- package/framework/scripts/lincoln_skill_prompt.py +48 -0
- package/framework/scripts/lincoln_trace.py +335 -0
- package/framework/scripts/lincoln_workflow.py +199 -0
- package/framework/scripts/list-active-lincoln-branches.sh +87 -0
- package/framework/scripts/package-lincoln-plugin.py +340 -0
- package/framework/scripts/run-infrastructure-tests.py +84 -0
- package/framework/scripts/stage_loader.py +1071 -0
- package/framework/scripts/static-check.sh +356 -0
- package/framework/scripts/sync-external-agents.sh +210 -0
- package/framework/scripts/sync-framework-package.mjs +218 -0
- package/framework/scripts/task_tool_guard.py +213 -0
- package/framework/scripts/track-artifacts.py +140 -0
- package/framework/scripts/validate_stage.py +350 -0
- package/{README.md → framework/tools/lincoln/README.md} +4 -4
- package/framework/tools/lincoln/package-lock.json +3349 -0
- package/framework/tools/lincoln/package.json +48 -0
- package/framework/tools/lincoln/src/cli.tsx +60 -0
- package/framework/tools/lincoln/src/components/AudioMeter.tsx +34 -0
- package/framework/tools/lincoln/src/components/CancelledScreen.tsx +11 -0
- package/framework/tools/lincoln/src/components/DevicesMenu.tsx +79 -0
- package/framework/tools/lincoln/src/components/HintBar.tsx +25 -0
- package/framework/tools/lincoln/src/components/LogView.tsx +36 -0
- package/framework/tools/lincoln/src/components/ModelMenu.tsx +122 -0
- package/framework/tools/lincoln/src/components/ReadyScreen.tsx +54 -0
- package/framework/tools/lincoln/src/components/RecordingApp.tsx +211 -0
- package/framework/tools/lincoln/src/components/RecordingScreen.tsx +41 -0
- package/framework/tools/lincoln/src/components/SelectMenu.tsx +53 -0
- package/framework/tools/lincoln/src/components/StatusPanel.tsx +52 -0
- package/framework/tools/lincoln/src/components/StopConfirmation.tsx +32 -0
- package/framework/tools/lincoln/src/config/args.ts +35 -0
- package/framework/tools/lincoln/src/config/loadConfig.ts +81 -0
- package/framework/tools/lincoln/src/config/resolveConfig.ts +85 -0
- package/framework/tools/lincoln/src/devices/listDevices.ts +11 -0
- package/framework/tools/lincoln/src/devices/parseDevices.ts +25 -0
- package/framework/tools/lincoln/src/hooks/useKeyHandler.ts +63 -0
- package/framework/tools/lincoln/src/main.tsx +4 -0
- package/framework/tools/lincoln/src/models/models.ts +34 -0
- package/framework/tools/lincoln/src/models/runWarmup.ts +46 -0
- package/framework/tools/lincoln/src/models/warmupProgress.ts +30 -0
- package/framework/tools/lincoln/src/recording/spawnRecorder.ts +127 -0
- package/framework/tools/lincoln/src/recording/useRecorder.ts +146 -0
- package/framework/tools/lincoln/src/utils/amplitudeToMeter.ts +4 -0
- package/framework/tools/lincoln/src/utils/formatDuration.ts +6 -0
- package/framework/tools/lincoln/src/utils/sessionId.ts +25 -0
- package/framework/tools/lincoln/tests/cli.test.tsx +52 -0
- package/framework/tools/lincoln/tests/components/AudioMeter.test.tsx +31 -0
- package/framework/tools/lincoln/tests/components/CancelledScreen.test.tsx +12 -0
- package/framework/tools/lincoln/tests/components/ReadyScreen.test.tsx +68 -0
- package/framework/tools/lincoln/tests/components/RecordingApp.menus.test.tsx +158 -0
- package/framework/tools/lincoln/tests/components/RecordingApp.test.tsx +248 -0
- package/framework/tools/lincoln/tests/components/RecordingScreen.test.tsx +37 -0
- package/framework/tools/lincoln/tests/components/SelectMenu.test.tsx +59 -0
- package/framework/tools/lincoln/tests/components/StopConfirmation.test.tsx +38 -0
- package/framework/tools/lincoln/tests/config/args.test.ts +49 -0
- package/framework/tools/lincoln/tests/config/loadConfig.test.ts +59 -0
- package/framework/tools/lincoln/tests/config/resolveConfig.test.ts +72 -0
- package/framework/tools/lincoln/tests/devices/parseDevices.test.ts +21 -0
- package/framework/tools/lincoln/tests/hooks/useKeyHandler.enabled.test.tsx +44 -0
- package/framework/tools/lincoln/tests/hooks/useKeyHandler.test.tsx +130 -0
- package/framework/tools/lincoln/tests/models/models.test.ts +27 -0
- package/framework/tools/lincoln/tests/models/warmupProgress.test.ts +25 -0
- package/framework/tools/lincoln/tests/recording/spawnRecorder.test.ts +85 -0
- package/framework/tools/lincoln/tests/recording/useRecorder.test.tsx +172 -0
- package/framework/tools/lincoln/tests/utils/amplitudeToMeter.test.ts +22 -0
- package/framework/tools/lincoln/tests/utils/formatDuration.test.ts +11 -0
- package/framework/tools/lincoln/tests/utils/sessionId.test.ts +30 -0
- package/framework/tools/lincoln/tsconfig.json +22 -0
- package/framework/tools/lincoln/tsconfig.tests.json +10 -0
- package/framework/tools/lincoln/tsup.config.ts +17 -0
- package/framework/tools/lincoln/vitest.config.ts +20 -0
- package/framework/tools/lincoln-installer/package-lock.json +3611 -0
- package/framework/tools/lincoln-installer/package.json +53 -0
- package/framework/tools/lincoln-installer/src/cli.tsx +63 -0
- package/framework/tools/lincoln-installer/src/components/HarnessSelectScreen.tsx +43 -0
- package/framework/tools/lincoln-installer/src/components/InstallerApp.tsx +98 -0
- package/framework/tools/lincoln-installer/src/components/OptionsScreen.tsx +56 -0
- package/framework/tools/lincoln-installer/src/components/ProgressScreen.tsx +25 -0
- package/framework/tools/lincoln-installer/src/components/ResultScreen.tsx +40 -0
- package/framework/tools/lincoln-installer/src/components/SelectMenu.tsx +73 -0
- package/framework/tools/lincoln-installer/src/components/SummaryScreen.tsx +49 -0
- package/framework/tools/lincoln-installer/src/components/UpdateApp.tsx +135 -0
- package/framework/tools/lincoln-installer/src/components/WelcomeScreen.tsx +22 -0
- package/framework/tools/lincoln-installer/src/config/args.ts +76 -0
- package/framework/tools/lincoln-installer/src/config/resolveConfig.ts +33 -0
- package/framework/tools/lincoln-installer/src/config/updateArgs.ts +67 -0
- package/framework/tools/lincoln-installer/src/hooks/useSetup.ts +149 -0
- package/framework/tools/lincoln-installer/src/main.tsx +11 -0
- package/framework/tools/lincoln-installer/src/types.ts +19 -0
- package/framework/tools/lincoln-installer/src/update-main.tsx +11 -0
- package/framework/tools/lincoln-installer/src/update.tsx +90 -0
- package/framework/tools/lincoln-installer/src/utils/detectHarness.ts +51 -0
- package/framework/tools/lincoln-installer/src/utils/runSetup.ts +62 -0
- package/framework/tools/lincoln-installer/src/utils/updateRelease.ts +454 -0
- package/framework/tools/lincoln-installer/tests/components/InstallerApp.test.tsx +88 -0
- package/framework/tools/lincoln-installer/tests/components/SelectMenu.test.tsx +26 -0
- package/framework/tools/lincoln-installer/tests/components/UpdateApp.test.tsx +170 -0
- package/framework/tools/lincoln-installer/tests/components/screens.test.tsx +310 -0
- package/framework/tools/lincoln-installer/tests/config/args.test.ts +66 -0
- package/framework/tools/lincoln-installer/tests/config/resolveConfig.test.ts +75 -0
- package/framework/tools/lincoln-installer/tests/config/updateArgs.test.ts +52 -0
- package/framework/tools/lincoln-installer/tests/hooks/useSetup.test.tsx +213 -0
- package/framework/tools/lincoln-installer/tests/update.test.tsx +108 -0
- package/framework/tools/lincoln-installer/tests/utils/detectHarness.test.ts +40 -0
- package/framework/tools/lincoln-installer/tests/utils/runSetup.test.ts +45 -0
- package/framework/tools/lincoln-installer/tests/utils/updateRelease.test.ts +559 -0
- package/framework/tools/lincoln-installer/tsconfig.json +22 -0
- package/framework/tools/lincoln-installer/tsconfig.tests.json +8 -0
- package/framework/tools/lincoln-installer/tsup.config.ts +17 -0
- package/framework/tools/lincoln-installer/vitest.config.ts +20 -0
- package/framework/tools/lincoln-record/Cargo.lock +1896 -0
- package/framework/tools/lincoln-record/Cargo.toml +24 -0
- package/framework/tools/lincoln-record/diarize/diarize.py +117 -0
- package/framework/tools/lincoln-record/diarize/merge.py +55 -0
- package/framework/tools/lincoln-record/src/audio/capture/microphone.rs +194 -0
- package/framework/tools/lincoln-record/src/audio/capture/mod.rs +1 -0
- package/framework/tools/lincoln-record/src/audio/devices.rs +20 -0
- package/framework/tools/lincoln-record/src/audio/mod.rs +5 -0
- package/framework/tools/lincoln-record/src/audio/resampler.rs +44 -0
- package/framework/tools/lincoln-record/src/audio/saver.rs +87 -0
- package/framework/tools/lincoln-record/src/audio/source.rs +104 -0
- package/framework/tools/lincoln-record/src/cli.rs +96 -0
- package/framework/tools/lincoln-record/src/config.rs +135 -0
- package/framework/tools/lincoln-record/src/lib.rs +6 -0
- package/framework/tools/lincoln-record/src/main.rs +242 -0
- package/framework/tools/lincoln-record/src/model/mod.rs +248 -0
- package/framework/tools/lincoln-record/src/output/metadata.rs +30 -0
- package/framework/tools/lincoln-record/src/output/mod.rs +2 -0
- package/framework/tools/lincoln-record/src/output/transcript.rs +42 -0
- package/framework/tools/lincoln-record/src/transcription/diarization.rs +64 -0
- package/framework/tools/lincoln-record/src/transcription/mod.rs +3 -0
- package/framework/tools/lincoln-record/src/transcription/provider.rs +43 -0
- package/framework/tools/lincoln-record/src/transcription/whisper.rs +176 -0
- package/framework/tools/lincoln-record/tests/cli_args.rs +103 -0
- package/framework/tools/lincoln-record/tests/config_test.rs +70 -0
- package/framework/tools/lincoln-record/tests/devices_test.rs +23 -0
- package/framework/tools/lincoln-record/tests/diarization_tests.rs +101 -0
- package/framework/tools/lincoln-record/tests/integration_tests.rs +44 -0
- package/framework/tools/lincoln-record/tests/model_test.rs +68 -0
- package/framework/tools/lincoln-record/tests/transcription_tests.rs +73 -0
- package/package.json +19 -13
|
@@ -0,0 +1,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())
|