@colin4k1024/tsp 2.4.0 → 2.4.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 (230) hide show
  1. package/README.md +87 -4
  2. package/bin/lib/post-install-bridge.js +2 -2
  3. package/bin/tsp-create.js +11 -11
  4. package/commands/team-help.md +2 -2
  5. package/commands/team-plan.md +1 -1
  6. package/commands/update-codemaps.md +3 -2
  7. package/docs/.vitepress/config.mts +199 -0
  8. package/docs/adr/ADR-001-doc-architecture-integration.md +33 -0
  9. package/docs/guides/README.md +5 -0
  10. package/docs/guides/installation.md +33 -0
  11. package/docs/guides/user-guide.md +36 -0
  12. package/docs/index.md +65 -0
  13. package/docs/memory/backlog.md +10 -0
  14. package/docs/memory/decisions.md +43 -0
  15. package/docs/memory/lessons-learned.md +87 -0
  16. package/docs/plans/2026-04-03-python-remnants-audit.md +265 -0
  17. package/docs/plans/2026-04-03-scripts-python-to-js-migration.md +372 -0
  18. package/docs/plans/2026-04-03-solo-delivery-execution-checklist.md +413 -0
  19. package/docs/plans/2026-04-03-solo-delivery-gap-plan.md +377 -0
  20. package/docs/plans/2026-04-03-team-skills-workflow-gates.md +548 -0
  21. package/docs/plans/2026-04-21-open-source-readiness-gap-plan.md +217 -0
  22. package/docs/plans/llm-surface-reduction-audit.md +147 -0
  23. package/docs/plans/llm-surface-reduction-execution-checklist.md +217 -0
  24. package/docs/plans/llm-surface-reduction-execution-history.md +124 -0
  25. package/docs/plans/team-skills-platform-migration.md +54 -0
  26. package/docs/presentation/README.md +42 -0
  27. package/docs/presentation/audience-presentation-route-map.md +84 -0
  28. package/docs/presentation/executive-briefing-talk-track.md +50 -0
  29. package/docs/presentation/generate_capability_matrix.py +396 -0
  30. package/docs/presentation/generate_ppt.py +354 -0
  31. package/docs/presentation/implementation-onboarding-brief.md +38 -0
  32. package/docs/presentation/presentation-talk-track.md +97 -0
  33. package/docs/presentation/vertical-scenario-route-map.md +99 -0
  34. package/docs/presentation/workshop-facilitator-guide.md +47 -0
  35. package/docs/runbooks/actionlint-workflow-gates.md +80 -0
  36. package/docs/runbooks/agent-governance.md +131 -0
  37. package/docs/runbooks/ai-eval-platform-demo-execution-log.md +147 -0
  38. package/docs/runbooks/ai-eval-platform-demo-script.md +136 -0
  39. package/docs/runbooks/ai-eval-platform-walkthrough.md +113 -0
  40. package/docs/runbooks/ai-pr-review-automation.md +56 -0
  41. package/docs/runbooks/api-breaking-change-gates.md +58 -0
  42. package/docs/runbooks/api-design-evolution-walkthrough.md +42 -0
  43. package/docs/runbooks/api-lint-gates.md +57 -0
  44. package/docs/runbooks/api-mocking-strategy-and-lifecycle-guide.md +47 -0
  45. package/docs/runbooks/architect-daily-operations.md +63 -0
  46. package/docs/runbooks/architect-design-conversation-example.md +83 -0
  47. package/docs/runbooks/artifact-attestation-gates.md +75 -0
  48. package/docs/runbooks/artifact-persistence.md +257 -0
  49. package/docs/runbooks/backend-engineer-daily-operations.md +63 -0
  50. package/docs/runbooks/batch-optimization-completion-checklist.md +104 -0
  51. package/docs/runbooks/biz-service-designer-end-to-end-conversation-example.md +5 -0
  52. package/docs/runbooks/biz-service-designer-toolkit.md +5 -0
  53. package/docs/runbooks/bug-fix-complete-walkthrough.md +60 -0
  54. package/docs/runbooks/build-failure-recovery-walkthrough.md +40 -0
  55. package/docs/runbooks/canary-decision-matrix.md +41 -0
  56. package/docs/runbooks/canary-staging-release-walkthrough.md +46 -0
  57. package/docs/runbooks/checkov-iac-gates.md +104 -0
  58. package/docs/runbooks/claude-code-review-workflow.md +72 -0
  59. package/docs/runbooks/claude-conversation-prompt-recipes.md +132 -0
  60. package/docs/runbooks/claude-end-to-end-conversation-example.md +198 -0
  61. package/docs/runbooks/claude-feature-development-guide.md +112 -0
  62. package/docs/runbooks/claude-quick-start.md +227 -0
  63. package/docs/runbooks/claude-usage-scenarios.md +176 -0
  64. package/docs/runbooks/code-review-collaboration-walkthrough.md +65 -0
  65. package/docs/runbooks/codeql-pr-security-gates.md +64 -0
  66. package/docs/runbooks/codex-end-to-end-conversation-example.md +166 -0
  67. package/docs/runbooks/codex-multi-agent-orchestration.md +65 -0
  68. package/docs/runbooks/codex-parallel-prompt-recipes.md +131 -0
  69. package/docs/runbooks/codex-quick-start.md +223 -0
  70. package/docs/runbooks/codex-usage-scenarios.md +168 -0
  71. package/docs/runbooks/codex-workflow-essentials.md +88 -0
  72. package/docs/runbooks/command-and-capability-matrix.md +162 -0
  73. package/docs/runbooks/conftest-policy-gates.md +84 -0
  74. package/docs/runbooks/consumer-driven-contract-testing-with-mock-alignment.md +45 -0
  75. package/docs/runbooks/contract-testing-playbook.md +78 -0
  76. package/docs/runbooks/cosign-signing-gates.md +71 -0
  77. package/docs/runbooks/cross-role-issue-triage-walkthrough.md +47 -0
  78. package/docs/runbooks/cursor-quick-start.md +123 -0
  79. package/docs/runbooks/custom-overlay.md +115 -0
  80. package/docs/runbooks/data-ml-pipeline-demo-execution-log.md +141 -0
  81. package/docs/runbooks/data-ml-pipeline-demo-script.md +102 -0
  82. package/docs/runbooks/data-ml-pipeline-walkthrough.md +119 -0
  83. package/docs/runbooks/data-observability-quality-demo-execution-log.md +36 -0
  84. package/docs/runbooks/data-observability-quality-demo-script.md +42 -0
  85. package/docs/runbooks/data-observability-quality-walkthrough.md +86 -0
  86. package/docs/runbooks/demo-deliverables-overview.md +278 -0
  87. package/docs/runbooks/demo-execution-log.md +530 -0
  88. package/docs/runbooks/demo-scenario.md +129 -0
  89. package/docs/runbooks/dependency-review-gates.md +63 -0
  90. package/docs/runbooks/dependency-update-automation.md +83 -0
  91. package/docs/runbooks/design-md-workflow.md +185 -0
  92. package/docs/runbooks/devops-engineer-daily-operations.md +60 -0
  93. package/docs/runbooks/devops-release-conversation-example.md +88 -0
  94. package/docs/runbooks/doc-architecture-integration.md +59 -0
  95. package/docs/runbooks/doc-architecture-quick-start.md +122 -0
  96. package/docs/runbooks/document-execution-audit.md +32 -0
  97. package/docs/runbooks/documentation-update-walkthrough.md +37 -0
  98. package/docs/runbooks/ecc-harness-usage.md +93 -0
  99. package/docs/runbooks/error-experience-usage.md +116 -0
  100. package/docs/runbooks/evolution-usage.md +162 -0
  101. package/docs/runbooks/executive-value-one-page.md +55 -0
  102. package/docs/runbooks/external-capability-approval-and-enablement-workflow.md +39 -0
  103. package/docs/runbooks/external-capability-intake.md +160 -0
  104. package/docs/runbooks/first-team-command-60-seconds.md +96 -0
  105. package/docs/runbooks/first-team-workflow-walkthrough.md +245 -0
  106. package/docs/runbooks/frontend-backend-integration-acceptance-checklist.md +46 -0
  107. package/docs/runbooks/frontend-backend-parallel-integration-walkthrough.md +48 -0
  108. package/docs/runbooks/frontend-bugfix-one-page.md +82 -0
  109. package/docs/runbooks/frontend-engineer-daily-operations.md +60 -0
  110. package/docs/runbooks/frontend-enterprise-style-profile.md +5 -0
  111. package/docs/runbooks/frontend-governance.md +47 -0
  112. package/docs/runbooks/frontend-refactor-walkthrough.md +42 -0
  113. package/docs/runbooks/git-pr-workflow.md +63 -0
  114. package/docs/runbooks/github-actions-supply-chain-demo-execution-log.md +158 -0
  115. package/docs/runbooks/github-actions-supply-chain-demo-script.md +150 -0
  116. package/docs/runbooks/github-actions-supply-chain-walkthrough.md +117 -0
  117. package/docs/runbooks/github-token-permissions-baseline.md +92 -0
  118. package/docs/runbooks/gitlab-manual-pipeline-release.md +5 -0
  119. package/docs/runbooks/gitlab-release-integration-playbook.md +5 -0
  120. package/docs/runbooks/gitnexus-code-intelligence-usage.md +133 -0
  121. package/docs/runbooks/graphify-knowledge-graph-usage.md +88 -0
  122. package/docs/runbooks/handoff-filling-guide-with-examples.md +70 -0
  123. package/docs/runbooks/handoff-governance.md +250 -0
  124. package/docs/runbooks/helm-unittest-playbook.md +101 -0
  125. package/docs/runbooks/hotfix-emergency-release-walkthrough.md +60 -0
  126. package/docs/runbooks/iac-kubernetes-platform-demo-execution-log.md +144 -0
  127. package/docs/runbooks/iac-kubernetes-platform-demo-script.md +130 -0
  128. package/docs/runbooks/iac-kubernetes-platform-walkthrough.md +120 -0
  129. package/docs/runbooks/implementation-onboarding-reading-path.md +67 -0
  130. package/docs/runbooks/in-toto-attestation-framework.md +94 -0
  131. package/docs/runbooks/incident-severity-triage-tree.md +43 -0
  132. package/docs/runbooks/incident-triage-one-page.md +65 -0
  133. package/docs/runbooks/internal-developer-platform-demo-execution-log.md +36 -0
  134. package/docs/runbooks/internal-developer-platform-demo-script.md +42 -0
  135. package/docs/runbooks/internal-developer-platform-walkthrough.md +91 -0
  136. package/docs/runbooks/karpathy-guidelines-usage.md +27 -0
  137. package/docs/runbooks/kubeconform-schema-gates.md +100 -0
  138. package/docs/runbooks/kubectl-server-dry-run-gates.md +103 -0
  139. package/docs/runbooks/kyverno-policy-gates.md +90 -0
  140. package/docs/runbooks/langfuse-and-observability-integration-guide.md +43 -0
  141. package/docs/runbooks/langfuse-coding-trace.md +44 -0
  142. package/docs/runbooks/mobile-miniapp-delivery-walkthrough.md +112 -0
  143. package/docs/runbooks/mobile-miniapp-demo-execution-log.md +139 -0
  144. package/docs/runbooks/mobile-miniapp-demo-script.md +129 -0
  145. package/docs/runbooks/multi-service-backend-integration-walkthrough.md +61 -0
  146. package/docs/runbooks/open-design-integration.md +163 -0
  147. package/docs/runbooks/open-source-release-checklist.md +90 -0
  148. package/docs/runbooks/opencode-quick-start.md +128 -0
  149. package/docs/runbooks/parallel-development-coordination-walkthrough.md +47 -0
  150. package/docs/runbooks/parallel-execution-usage.md +179 -0
  151. package/docs/runbooks/platform-capability-demo-execution-log.md +184 -0
  152. package/docs/runbooks/platform-capability-demo-script.md +192 -0
  153. package/docs/runbooks/plugin-extension-platform-demo-execution-log.md +136 -0
  154. package/docs/runbooks/plugin-extension-platform-demo-script.md +102 -0
  155. package/docs/runbooks/plugin-extension-platform-walkthrough.md +111 -0
  156. package/docs/runbooks/policy-controller-gates.md +75 -0
  157. package/docs/runbooks/post-rollback-verification-checklist.md +37 -0
  158. package/docs/runbooks/pre-release-checklist.md +50 -0
  159. package/docs/runbooks/product-manager-clarification-conversation-example.md +90 -0
  160. package/docs/runbooks/product-manager-daily-operations.md +60 -0
  161. package/docs/runbooks/production-incident-response-walkthrough.md +50 -0
  162. package/docs/runbooks/project-claude-design-rationale.md +188 -0
  163. package/docs/runbooks/project-manager-daily-operations.md +61 -0
  164. package/docs/runbooks/project-manager-planning-conversation-example.md +82 -0
  165. package/docs/runbooks/project-onboarding.md +452 -0
  166. package/docs/runbooks/qa-engineer-daily-operations.md +63 -0
  167. package/docs/runbooks/qa-review-conversation-example.md +87 -0
  168. package/docs/runbooks/release-closure-one-page.md +65 -0
  169. package/docs/runbooks/release-governance-reading-path.md +56 -0
  170. package/docs/runbooks/release-notes-automation.md +48 -0
  171. package/docs/runbooks/release-rollback-recovery-walkthrough.md +47 -0
  172. package/docs/runbooks/requirement-clarity-and-scope-walkthrough.md +46 -0
  173. package/docs/runbooks/reviewdog-pr-gates.md +49 -0
  174. package/docs/runbooks/role-prompt-recipes.md +130 -0
  175. package/docs/runbooks/rtk-integration-intake.md +45 -0
  176. package/docs/runbooks/rtk-token-optimization-usage.md +107 -0
  177. package/docs/runbooks/runner-egress-hardening.md +81 -0
  178. package/docs/runbooks/runtime-capabilities-overview.md +113 -0
  179. package/docs/runbooks/sbom-generation-gates.md +71 -0
  180. package/docs/runbooks/scorecard-supply-chain-gates.md +82 -0
  181. package/docs/runbooks/secret-scanning-gates.md +85 -0
  182. package/docs/runbooks/security-compliance-platform-demo-execution-log.md +36 -0
  183. package/docs/runbooks/security-compliance-platform-demo-script.md +49 -0
  184. package/docs/runbooks/security-compliance-platform-walkthrough.md +98 -0
  185. package/docs/runbooks/slsa-generator-patterns.md +73 -0
  186. package/docs/runbooks/slsa-verification-gates.md +75 -0
  187. package/docs/runbooks/solo-delivery-mode.md +142 -0
  188. package/docs/runbooks/solo-delivery-one-page.md +111 -0
  189. package/docs/runbooks/specialist-commands-playbook.md +85 -0
  190. package/docs/runbooks/sub-agent-invocation-map.md +144 -0
  191. package/docs/runbooks/system-architecture-design-walkthrough.md +49 -0
  192. package/docs/runbooks/team-closeout-example.md +73 -0
  193. package/docs/runbooks/team-command-output-contracts.md +358 -0
  194. package/docs/runbooks/team-commands-quick-prompts.md +125 -0
  195. package/docs/runbooks/team-execute-example.md +63 -0
  196. package/docs/runbooks/team-handoff-example.md +49 -0
  197. package/docs/runbooks/team-intake-example.md +70 -0
  198. package/docs/runbooks/team-plan-example.md +62 -0
  199. package/docs/runbooks/team-release-example.md +63 -0
  200. package/docs/runbooks/team-review-example.md +61 -0
  201. package/docs/runbooks/team-skills-test-run.md +184 -0
  202. package/docs/runbooks/team-skills-usage.md +336 -0
  203. package/docs/runbooks/team-training-reading-path.md +64 -0
  204. package/docs/runbooks/tech-lead-closure-conversation-example.md +78 -0
  205. package/docs/runbooks/tech-lead-daily-operations.md +67 -0
  206. package/docs/runbooks/trivy-security-gates.md +79 -0
  207. package/docs/runbooks/troubleshooting.md +234 -0
  208. package/docs/runbooks/vertical-scenario-capability-matrix.md +107 -0
  209. package/docs/runbooks/witness-policy-gates.md +78 -0
  210. package/docs/runbooks/zizmor-workflow-audits.md +81 -0
  211. package/manifests/install-components.json +9 -1
  212. package/manifests/install-modules.json +38 -2
  213. package/manifests/install-profiles.json +2 -0
  214. package/package.json +4 -1
  215. package/scripts/gitnexus-preflight.js +187 -0
  216. package/scripts/install-apply.js +9 -0
  217. package/scripts/install-open-design.js +206 -0
  218. package/scripts/install-plan.js +17 -0
  219. package/scripts/lib/install/apply.js +31 -0
  220. package/scripts/lib/install-executor.js +56 -0
  221. package/scripts/lib/team-skills-data.json +7 -6
  222. package/scripts/project-progress.js +852 -0
  223. package/scripts/release-health-summary.js +49 -7
  224. package/scripts/release.sh +1 -1
  225. package/scripts/validate-packed-tarball.js +25 -0
  226. package/scripts/workflow-help.js +3 -3
  227. package/skills/gitnexus/SKILL.md +60 -0
  228. package/skills/gitnexus/agents/openai.yaml +4 -0
  229. package/skills/open-design/SKILL.md +87 -0
  230. package/skills/open-design/agents/openai.yaml +4 -0
@@ -0,0 +1,852 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('fs');
5
+ const os = require('os');
6
+ const path = require('path');
7
+
8
+ const { analyzeTask } = require('./workflow-help');
9
+
10
+ const TASK_DIR_PATTERN = /^\d{4}-\d{2}-\d{2}-.+/;
11
+ const DEFAULT_REGISTRY = path.join(os.homedir(), '.claude', 'homunculus', 'projects.json');
12
+ const DEFAULT_MAX_DEPTH = 2;
13
+ const DEFAULT_STALE_DAYS = 7;
14
+ const DAY_MS = 24 * 60 * 60 * 1000;
15
+ const IGNORED_SCAN_DIRS = new Set([
16
+ '.git',
17
+ '.hg',
18
+ '.svn',
19
+ '.cache',
20
+ '.next',
21
+ '.turbo',
22
+ 'dist',
23
+ 'build',
24
+ 'coverage',
25
+ 'node_modules',
26
+ 'target',
27
+ 'vendor',
28
+ ]);
29
+
30
+ const ARTIFACTS = Object.freeze([
31
+ ['prd', 'prd.md'],
32
+ ['deliveryPlan', 'delivery-plan.md'],
33
+ ['archDesign', 'arch-design.md'],
34
+ ['executeLog', 'execute-log.md'],
35
+ ['testPlan', 'test-plan.md'],
36
+ ['launchAcceptance', 'launch-acceptance.md'],
37
+ ['deploymentContext', 'deployment-context.md'],
38
+ ['releasePlan', 'release-plan.md'],
39
+ ['closeout', 'closeout-summary.md'],
40
+ ]);
41
+
42
+ const MISSING_LABELS = Object.freeze({
43
+ prd: 'prd.md',
44
+ deliveryPlan: 'delivery-plan.md',
45
+ archDesign: 'arch-design.md',
46
+ handoff: 'handoffs/*.md',
47
+ executeLog: 'execute-log.md',
48
+ testPlan: 'test-plan.md',
49
+ launchAcceptance: 'launch-acceptance.md',
50
+ deploymentContext: 'deployment-context.md',
51
+ releasePlan: 'release-plan.md',
52
+ closeout: 'closeout-summary.md',
53
+ });
54
+
55
+ const SEVERITY_RANK = Object.freeze({
56
+ none: 0,
57
+ info: 1,
58
+ warning: 2,
59
+ critical: 3,
60
+ });
61
+
62
+ function parseArgs(argv) {
63
+ const options = {
64
+ help: false,
65
+ json: false,
66
+ registry: DEFAULT_REGISTRY,
67
+ projects: [],
68
+ scanRoots: [],
69
+ maxDepth: DEFAULT_MAX_DEPTH,
70
+ failOnRisk: false,
71
+ staleDays: DEFAULT_STALE_DAYS,
72
+ };
73
+
74
+ for (let index = 0; index < argv.length; index += 1) {
75
+ const arg = argv[index];
76
+ if (arg === '--help' || arg === '-h') {
77
+ options.help = true;
78
+ continue;
79
+ }
80
+ if (arg === '--json') {
81
+ options.json = true;
82
+ continue;
83
+ }
84
+ if (arg === '--fail-on-risk') {
85
+ options.failOnRisk = true;
86
+ continue;
87
+ }
88
+ if (arg === '--registry' && argv[index + 1]) {
89
+ options.registry = path.resolve(argv[index + 1]);
90
+ index += 1;
91
+ continue;
92
+ }
93
+ if (arg === '--project' && argv[index + 1]) {
94
+ options.projects.push(path.resolve(argv[index + 1]));
95
+ index += 1;
96
+ continue;
97
+ }
98
+ if (arg === '--scan-root' && argv[index + 1]) {
99
+ options.scanRoots.push(path.resolve(argv[index + 1]));
100
+ index += 1;
101
+ continue;
102
+ }
103
+ if (arg === '--max-depth' && argv[index + 1]) {
104
+ const maxDepth = Number(argv[index + 1]);
105
+ if (!Number.isInteger(maxDepth) || maxDepth < 0) {
106
+ throw new Error('--max-depth must be a non-negative integer');
107
+ }
108
+ options.maxDepth = maxDepth;
109
+ index += 1;
110
+ continue;
111
+ }
112
+ if (arg === '--stale-days' && argv[index + 1]) {
113
+ const staleDays = Number(argv[index + 1]);
114
+ if (!Number.isInteger(staleDays) || staleDays < 0) {
115
+ throw new Error('--stale-days must be a non-negative integer');
116
+ }
117
+ options.staleDays = staleDays;
118
+ index += 1;
119
+ continue;
120
+ }
121
+ throw new Error(`Unknown argument: ${arg}`);
122
+ }
123
+
124
+ return options;
125
+ }
126
+
127
+ function getHelpText() {
128
+ return [
129
+ 'Usage: node scripts/project-progress.js [options]',
130
+ '',
131
+ 'Options:',
132
+ ' --registry <path> Project registry JSON. Defaults to ~/.claude/homunculus/projects.json.',
133
+ ' --project <path> Add one project root. Repeatable.',
134
+ ' --scan-root <path> Scan a directory for Team Skills project roots. Repeatable.',
135
+ ' --max-depth <n> Max scan depth for --scan-root. Defaults to 2.',
136
+ ' --stale-days <n> Mark in-progress tasks stale after n days. Defaults to 7.',
137
+ ' --fail-on-risk Exit non-zero when blocked or stale in-progress tasks exist.',
138
+ ' --json Emit structured JSON output.',
139
+ ' -h, --help Show this help message.',
140
+ '',
141
+ 'The report is read-only and derives progress from docs/artifacts and docs/memory/project-context.md.',
142
+ ].join('\n');
143
+ }
144
+
145
+ function exists(targetPath) {
146
+ return fs.existsSync(targetPath);
147
+ }
148
+
149
+ function readText(filePath) {
150
+ return fs.readFileSync(filePath, 'utf8');
151
+ }
152
+
153
+ function safeReadJson(filePath) {
154
+ if (!exists(filePath)) {
155
+ return null;
156
+ }
157
+ return JSON.parse(readText(filePath));
158
+ }
159
+
160
+ function loadRegistry(registryPath) {
161
+ const payload = safeReadJson(registryPath);
162
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
163
+ return [];
164
+ }
165
+
166
+ return Object.entries(payload)
167
+ .filter(([, value]) => value && typeof value === 'object')
168
+ .map(([id, value]) => ({
169
+ id,
170
+ name: value.name || id,
171
+ root: value.root || '',
172
+ remote: value.remote || null,
173
+ lastSeen: value.last_seen || value.lastSeen || null,
174
+ source: 'registry',
175
+ }));
176
+ }
177
+
178
+ function projectLooksTracked(projectRoot) {
179
+ return exists(path.join(projectRoot, 'docs', 'artifacts'))
180
+ || exists(path.join(projectRoot, 'docs', 'memory', 'project-context.md'));
181
+ }
182
+
183
+ function scanProjects(scanRoot, maxDepth) {
184
+ if (!exists(scanRoot) || !fs.statSync(scanRoot).isDirectory()) {
185
+ return [];
186
+ }
187
+
188
+ const found = [];
189
+
190
+ function visit(currentPath, depth) {
191
+ if (projectLooksTracked(currentPath)) {
192
+ found.push({
193
+ id: null,
194
+ name: path.basename(currentPath),
195
+ root: currentPath,
196
+ remote: null,
197
+ lastSeen: null,
198
+ source: 'scan',
199
+ });
200
+ return;
201
+ }
202
+
203
+ if (depth >= maxDepth) {
204
+ return;
205
+ }
206
+
207
+ let entries = [];
208
+ try {
209
+ entries = fs.readdirSync(currentPath, { withFileTypes: true });
210
+ } catch (_error) {
211
+ return;
212
+ }
213
+
214
+ for (const entry of entries) {
215
+ if (!entry.isDirectory() || IGNORED_SCAN_DIRS.has(entry.name)) {
216
+ continue;
217
+ }
218
+ visit(path.join(currentPath, entry.name), depth + 1);
219
+ }
220
+ }
221
+
222
+ visit(path.resolve(scanRoot), 0);
223
+ return found;
224
+ }
225
+
226
+ function normalizeProjectEntry(entry) {
227
+ const root = entry.root ? path.resolve(entry.root) : '';
228
+ return {
229
+ id: entry.id || (root ? path.basename(root) : 'unknown'),
230
+ name: entry.name || (root ? path.basename(root) : entry.id || 'unknown'),
231
+ root,
232
+ remote: entry.remote || null,
233
+ lastSeen: entry.lastSeen || null,
234
+ source: entry.source || 'explicit',
235
+ };
236
+ }
237
+
238
+ function collectProjectEntries(options) {
239
+ const entries = [];
240
+ const errors = [];
241
+
242
+ try {
243
+ entries.push(...loadRegistry(options.registry));
244
+ } catch (error) {
245
+ errors.push({
246
+ source: options.registry,
247
+ message: `Failed to read registry: ${error.message}`,
248
+ });
249
+ }
250
+
251
+ for (const projectPath of options.projects) {
252
+ entries.push({
253
+ id: null,
254
+ name: path.basename(projectPath),
255
+ root: projectPath,
256
+ remote: null,
257
+ lastSeen: null,
258
+ source: 'explicit',
259
+ });
260
+ }
261
+
262
+ for (const scanRoot of options.scanRoots) {
263
+ entries.push(...scanProjects(scanRoot, options.maxDepth));
264
+ }
265
+
266
+ const deduped = [];
267
+ const seen = new Set();
268
+ for (const rawEntry of entries) {
269
+ const entry = normalizeProjectEntry(rawEntry);
270
+ const key = entry.root || `id:${entry.id}`;
271
+ if (seen.has(key)) {
272
+ continue;
273
+ }
274
+ seen.add(key);
275
+ deduped.push(entry);
276
+ }
277
+
278
+ return {
279
+ projects: deduped,
280
+ errors,
281
+ };
282
+ }
283
+
284
+ function listTaskDirs(projectRoot) {
285
+ const artifactsRoot = path.join(projectRoot, 'docs', 'artifacts');
286
+ if (!exists(artifactsRoot)) {
287
+ return [];
288
+ }
289
+
290
+ return fs
291
+ .readdirSync(artifactsRoot, { withFileTypes: true })
292
+ .filter((entry) => entry.isDirectory() && TASK_DIR_PATTERN.test(entry.name))
293
+ .map((entry) => path.join(artifactsRoot, entry.name))
294
+ .sort();
295
+ }
296
+
297
+ function hasHandoff(taskDir) {
298
+ const handoffDir = path.join(taskDir, 'handoffs');
299
+ if (!exists(handoffDir)) {
300
+ return false;
301
+ }
302
+ return fs.readdirSync(handoffDir).some((name) => name.endsWith('.md'));
303
+ }
304
+
305
+ function inspectArtifacts(taskDir) {
306
+ const result = {};
307
+ for (const [key, fileName] of ARTIFACTS) {
308
+ result[key] = exists(path.join(taskDir, fileName));
309
+ }
310
+ result.handoff = hasHandoff(taskDir);
311
+ return result;
312
+ }
313
+
314
+ function inferTaskProgress(artifacts) {
315
+ if (artifacts.closeout) {
316
+ return { phase: 'closed', progress: 100 };
317
+ }
318
+ if (artifacts.deploymentContext && artifacts.releasePlan) {
319
+ return { phase: 'release', progress: 85 };
320
+ }
321
+ if (artifacts.testPlan && artifacts.launchAcceptance) {
322
+ return { phase: 'review', progress: 70 };
323
+ }
324
+ if (artifacts.executeLog) {
325
+ return { phase: 'execute', progress: 55 };
326
+ }
327
+ if (artifacts.handoff) {
328
+ return { phase: 'handoff-ready', progress: 40 };
329
+ }
330
+ if (artifacts.deliveryPlan) {
331
+ return { phase: 'plan', progress: 30 };
332
+ }
333
+ if (artifacts.prd) {
334
+ return { phase: 'intake', progress: 15 };
335
+ }
336
+ return { phase: 'untracked', progress: 0 };
337
+ }
338
+
339
+ function missingArtifacts(artifacts) {
340
+ return Object.keys(MISSING_LABELS)
341
+ .filter((key) => !artifacts[key])
342
+ .map((key) => MISSING_LABELS[key]);
343
+ }
344
+
345
+ function parseTaskDirName(taskDir) {
346
+ const name = path.basename(taskDir);
347
+ const match = name.match(/^(\d{4}-\d{2}-\d{2})-(.+)$/);
348
+ return {
349
+ id: name,
350
+ date: match ? match[1] : null,
351
+ slug: match ? match[2] : name,
352
+ };
353
+ }
354
+
355
+ function extractSectionBody(text, heading) {
356
+ const escaped = heading.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
357
+ const match = text.match(new RegExp(`^##\\s+${escaped}\\s*$\\n+([\\s\\S]*?)(?=\\n##\\s+|$)`, 'm'));
358
+ return match ? match[1].trim() : '';
359
+ }
360
+
361
+ function parseListSection(body) {
362
+ if (!body) {
363
+ return [];
364
+ }
365
+ return body
366
+ .split('\n')
367
+ .map((line) => line.trim().replace(/^[-*•]\s*/, '').trim())
368
+ .filter(Boolean)
369
+ .filter((line) => ![
370
+ '待补齐',
371
+ '暂无',
372
+ 'n/a',
373
+ 'na',
374
+ 'none',
375
+ 'tbd',
376
+ 'no active risk',
377
+ 'no active risks',
378
+ 'no active operational risk',
379
+ ].includes(line.toLowerCase()));
380
+ }
381
+
382
+ function parseProjectContext(projectRoot) {
383
+ const projectContextPath = path.join(projectRoot, 'docs', 'memory', 'project-context.md');
384
+ if (!exists(projectContextPath)) {
385
+ return {
386
+ phase: null,
387
+ currentTask: null,
388
+ risks: [],
389
+ nextSteps: [],
390
+ };
391
+ }
392
+
393
+ const text = readText(projectContextPath);
394
+ const phase = parseListSection(extractSectionBody(text, '当前阶段'))[0] || null;
395
+ const currentTask = parseListSection(extractSectionBody(text, '当前活跃任务'))[0] || null;
396
+ return {
397
+ phase,
398
+ currentTask,
399
+ risks: parseListSection(extractSectionBody(text, '活跃风险')),
400
+ nextSteps: parseListSection(extractSectionBody(text, '下一步建议')),
401
+ };
402
+ }
403
+
404
+ function analyzeNextStep(projectRoot, taskDir) {
405
+ try {
406
+ const result = analyzeTask({
407
+ cwd: projectRoot,
408
+ taskDir,
409
+ json: true,
410
+ help: false,
411
+ preferQuick: false,
412
+ });
413
+ return {
414
+ recommendedCommand: result.recommendedCommand || null,
415
+ missingPrerequisites: Array.isArray(result.missingPrerequisites) ? result.missingPrerequisites : [],
416
+ reason: result.reason || null,
417
+ };
418
+ } catch (_error) {
419
+ return {
420
+ recommendedCommand: null,
421
+ missingPrerequisites: [],
422
+ reason: null,
423
+ };
424
+ }
425
+ }
426
+
427
+ function inspectTask(projectRoot, taskDir) {
428
+ const task = parseTaskDirName(taskDir);
429
+ const artifacts = inspectArtifacts(taskDir);
430
+ const inferred = inferTaskProgress(artifacts);
431
+ const nextStep = analyzeNextStep(projectRoot, taskDir);
432
+ return {
433
+ ...task,
434
+ path: taskDir,
435
+ phase: inferred.phase,
436
+ progress: inferred.progress,
437
+ artifacts,
438
+ missing: missingArtifacts(artifacts),
439
+ recommendedCommand: nextStep.recommendedCommand,
440
+ nextStep,
441
+ };
442
+ }
443
+
444
+ function highestSeverity(values) {
445
+ return values.reduce((highest, value) => (
446
+ SEVERITY_RANK[value] > SEVERITY_RANK[highest] ? value : highest
447
+ ), 'none');
448
+ }
449
+
450
+ function ageDaysFromTaskDate(taskDate, now) {
451
+ if (!taskDate) {
452
+ return null;
453
+ }
454
+ const parsed = new Date(`${taskDate}T00:00:00.000Z`);
455
+ if (Number.isNaN(parsed.getTime())) {
456
+ return null;
457
+ }
458
+ const current = now instanceof Date ? now : new Date(now || Date.now());
459
+ const currentDay = Date.UTC(
460
+ current.getUTCFullYear(),
461
+ current.getUTCMonth(),
462
+ current.getUTCDate(),
463
+ );
464
+ return Math.floor((currentDay - parsed.getTime()) / DAY_MS);
465
+ }
466
+
467
+ function requiredEvidenceSignal(task) {
468
+ if (task.phase === 'untracked' && !task.artifacts.prd) {
469
+ return {
470
+ code: 'missing-prd',
471
+ severity: 'critical',
472
+ message: 'Task has no prd.md intake evidence.',
473
+ };
474
+ }
475
+ if (task.phase === 'intake' && !task.artifacts.deliveryPlan) {
476
+ return {
477
+ code: 'missing-delivery-plan',
478
+ severity: 'critical',
479
+ message: 'Task has intake evidence but no delivery-plan.md.',
480
+ };
481
+ }
482
+ if (task.phase === 'plan' && !task.artifacts.handoff) {
483
+ return {
484
+ code: 'missing-handoff',
485
+ severity: 'critical',
486
+ message: 'Task has a delivery plan but no handoff evidence.',
487
+ };
488
+ }
489
+ return null;
490
+ }
491
+
492
+ function activeRiskSignals(projectContext) {
493
+ return (projectContext.risks || []).map((risk) => ({
494
+ code: 'active-risk',
495
+ severity: 'warning',
496
+ message: `Project context risk: ${risk}`,
497
+ }));
498
+ }
499
+
500
+ function monitorTask(task, projectContext, projectLatestTaskId, options = {}) {
501
+ if (task.progress === 100) {
502
+ return {
503
+ status: 'closed',
504
+ severity: 'none',
505
+ signals: [],
506
+ recommendedAction: 'Task is closed.',
507
+ };
508
+ }
509
+
510
+ const signals = [];
511
+ const evidenceSignal = requiredEvidenceSignal(task);
512
+ if (evidenceSignal) {
513
+ signals.push(evidenceSignal);
514
+ }
515
+
516
+ if (task.nextStep && task.nextStep.missingPrerequisites.length > 0) {
517
+ signals.push({
518
+ code: 'workflow-prerequisite-gap',
519
+ severity: 'critical',
520
+ message: task.nextStep.missingPrerequisites[0],
521
+ });
522
+ }
523
+
524
+ const staleDays = Number.isInteger(options.staleDays) ? options.staleDays : DEFAULT_STALE_DAYS;
525
+ const ageDays = ageDaysFromTaskDate(task.date, options.now);
526
+ if (ageDays !== null && ageDays > staleDays) {
527
+ signals.push({
528
+ code: 'stale-task',
529
+ severity: 'warning',
530
+ message: `Task has been in progress for ${ageDays} days, above the ${staleDays} day threshold.`,
531
+ });
532
+ }
533
+
534
+ signals.push(...activeRiskSignals(projectContext));
535
+
536
+ if (
537
+ projectContext.currentTask
538
+ && projectLatestTaskId
539
+ && task.id === projectLatestTaskId
540
+ && projectContext.currentTask !== projectLatestTaskId
541
+ ) {
542
+ signals.push({
543
+ code: 'current-task-mismatch',
544
+ severity: 'warning',
545
+ message: `Project context current task is ${projectContext.currentTask}, but the latest artifact task is ${projectLatestTaskId}.`,
546
+ });
547
+ }
548
+
549
+ const severity = highestSeverity(signals.map((signal) => signal.severity));
550
+ let status = 'healthy';
551
+ if (signals.some((signal) => (
552
+ signal.code === 'missing-prd'
553
+ || signal.code === 'missing-delivery-plan'
554
+ || signal.code === 'missing-handoff'
555
+ || signal.code === 'workflow-prerequisite-gap'
556
+ ))) {
557
+ status = 'blocked';
558
+ } else if (signals.some((signal) => signal.code === 'stale-task')) {
559
+ status = 'stale';
560
+ } else if (signals.length > 0) {
561
+ status = 'atRisk';
562
+ }
563
+
564
+ return {
565
+ status,
566
+ severity,
567
+ signals,
568
+ recommendedAction: task.recommendedCommand
569
+ ? `Run ${task.recommendedCommand}.`
570
+ : 'Review task artifacts and project context.',
571
+ };
572
+ }
573
+
574
+ function applyMonitoring(tasks, projectContext, options = {}) {
575
+ const latestTask = tasks[tasks.length - 1] || null;
576
+ const latestTaskId = latestTask ? latestTask.id : null;
577
+
578
+ return tasks.map((task) => ({
579
+ ...task,
580
+ monitoring: monitorTask(task, projectContext, latestTaskId, options),
581
+ }));
582
+ }
583
+
584
+ function summarizeTasks(tasks) {
585
+ const totalTasks = tasks.length;
586
+ const closedTasks = tasks.filter((task) => task.progress === 100).length;
587
+ const activeTasks = totalTasks - closedTasks;
588
+ const averageProgress = totalTasks
589
+ ? Math.round(tasks.reduce((sum, task) => sum + task.progress, 0) / totalTasks)
590
+ : 0;
591
+
592
+ return {
593
+ totalTasks,
594
+ activeTasks,
595
+ closedTasks,
596
+ averageProgress,
597
+ };
598
+ }
599
+
600
+ function inspectProject(project, options = {}) {
601
+ if (!project.root || !exists(project.root)) {
602
+ return {
603
+ ...project,
604
+ status: 'missing',
605
+ summary: summarizeTasks([]),
606
+ currentContext: { phase: null, currentTask: null, risks: [], nextSteps: [] },
607
+ tasks: [],
608
+ };
609
+ }
610
+
611
+ if (!fs.statSync(project.root).isDirectory()) {
612
+ return {
613
+ ...project,
614
+ status: 'missing',
615
+ summary: summarizeTasks([]),
616
+ currentContext: { phase: null, currentTask: null, risks: [], nextSteps: [] },
617
+ tasks: [],
618
+ };
619
+ }
620
+
621
+ try {
622
+ const taskDirs = listTaskDirs(project.root);
623
+ const currentContext = parseProjectContext(project.root);
624
+ const inspectedTasks = taskDirs.map((taskDir) => inspectTask(project.root, taskDir));
625
+ const tasks = applyMonitoring(inspectedTasks, currentContext, options);
626
+ const summary = summarizeTasks(tasks);
627
+ const status = tasks.length === 0
628
+ ? 'untracked'
629
+ : (summary.closedTasks === summary.totalTasks ? 'closed' : 'active');
630
+
631
+ return {
632
+ ...project,
633
+ status,
634
+ summary,
635
+ currentContext,
636
+ tasks,
637
+ };
638
+ } catch (error) {
639
+ return {
640
+ ...project,
641
+ status: 'error',
642
+ summary: summarizeTasks([]),
643
+ currentContext: { phase: null, currentTask: null, risks: [], nextSteps: [] },
644
+ tasks: [],
645
+ error: error.message,
646
+ };
647
+ }
648
+ }
649
+
650
+ function summarizeMonitoring(projects) {
651
+ const tasksWithProject = projects.flatMap((project) => (
652
+ (project.tasks || []).map((task) => ({ project, task }))
653
+ ));
654
+ const inProgress = tasksWithProject.filter(({ task }) => task.progress < 100);
655
+ const blocked = inProgress.filter(({ task }) => task.monitoring && task.monitoring.status === 'blocked');
656
+ const stale = inProgress.filter(({ task }) => task.monitoring && task.monitoring.status === 'stale');
657
+ const atRisk = inProgress.filter(({ task }) => task.monitoring && task.monitoring.status === 'atRisk');
658
+ const needsAttention = inProgress
659
+ .filter(({ task }) => task.monitoring && task.monitoring.status !== 'healthy')
660
+ .map(({ project, task }) => ({
661
+ project: project.name,
662
+ root: project.root,
663
+ task: task.id,
664
+ status: task.monitoring.status,
665
+ severity: task.monitoring.severity,
666
+ signals: task.monitoring.signals.map((signal) => signal.code),
667
+ recommendedAction: task.monitoring.recommendedAction,
668
+ }));
669
+
670
+ return {
671
+ inProgressTasks: inProgress.length,
672
+ healthyTasks: inProgress.filter(({ task }) => task.monitoring && task.monitoring.status === 'healthy').length,
673
+ blockedTasks: blocked.length,
674
+ staleTasks: stale.length,
675
+ atRiskTasks: atRisk.length,
676
+ highestSeverity: highestSeverity(needsAttention.map((item) => item.severity)),
677
+ needsAttention,
678
+ };
679
+ }
680
+
681
+ function buildReport(options) {
682
+ const collected = collectProjectEntries(options);
683
+ const projects = collected.projects.map((project) => inspectProject(project, options));
684
+ const errors = [...collected.errors];
685
+ for (const project of projects) {
686
+ if (project.status === 'error') {
687
+ errors.push({
688
+ project: project.name,
689
+ root: project.root,
690
+ message: project.error,
691
+ });
692
+ }
693
+ }
694
+
695
+ return {
696
+ generatedAt: new Date().toISOString(),
697
+ monitoringSummary: summarizeMonitoring(projects),
698
+ projects,
699
+ errors,
700
+ };
701
+ }
702
+
703
+ function padRight(value, width) {
704
+ const text = String(value === null || value === undefined ? '' : value);
705
+ if (text.length >= width) {
706
+ return text;
707
+ }
708
+ return text + ' '.repeat(width - text.length);
709
+ }
710
+
711
+ function renderTable(rows, columns) {
712
+ const widths = columns.map((column) => Math.max(
713
+ column.label.length,
714
+ ...rows.map((row) => String(row[column.key] || '').length),
715
+ ));
716
+ const header = columns.map((column, index) => padRight(column.label, widths[index])).join(' ');
717
+ const divider = widths.map((width) => '-'.repeat(width)).join(' ');
718
+ const body = rows.map((row) => columns
719
+ .map((column, index) => padRight(row[column.key] || '', widths[index]))
720
+ .join(' '));
721
+ return [header, divider, ...body].join('\n');
722
+ }
723
+
724
+ function formatHumanReport(report) {
725
+ const lines = [];
726
+ lines.push(`Project progress snapshot (${report.generatedAt})`);
727
+ lines.push('');
728
+
729
+ if (!report.projects.length) {
730
+ lines.push('No projects found.');
731
+ } else {
732
+ const projectRows = report.projects.map((project) => ({
733
+ project: project.name,
734
+ status: project.status,
735
+ tasks: String(project.summary.totalTasks),
736
+ active: String(project.summary.activeTasks),
737
+ closed: String(project.summary.closedTasks),
738
+ avg: `${project.summary.averageProgress}%`,
739
+ monitor: project.tasks.length
740
+ ? highestSeverity(project.tasks.map((task) => task.monitoring?.severity || 'none'))
741
+ : '-',
742
+ current: project.currentContext.currentTask || '-',
743
+ phase: project.currentContext.phase || '-',
744
+ root: project.root || '-',
745
+ }));
746
+ lines.push(renderTable(projectRows, [
747
+ { key: 'project', label: 'Project' },
748
+ { key: 'status', label: 'Status' },
749
+ { key: 'tasks', label: 'Tasks' },
750
+ { key: 'active', label: 'Active' },
751
+ { key: 'closed', label: 'Closed' },
752
+ { key: 'avg', label: 'Avg' },
753
+ { key: 'monitor', label: 'Monitor' },
754
+ { key: 'current', label: 'Current Task' },
755
+ { key: 'phase', label: 'Context Phase' },
756
+ { key: 'root', label: 'Root' },
757
+ ]));
758
+ }
759
+
760
+ for (const project of report.projects) {
761
+ lines.push('');
762
+ lines.push(`## ${project.name}`);
763
+ if (project.error) {
764
+ lines.push(`Error: ${project.error}`);
765
+ }
766
+ if (!project.tasks.length) {
767
+ lines.push(project.status === 'missing' ? 'Project root is missing.' : 'No task artifacts found.');
768
+ continue;
769
+ }
770
+
771
+ const taskRows = project.tasks.map((task) => ({
772
+ task: task.id,
773
+ phase: task.phase,
774
+ progress: `${task.progress}%`,
775
+ monitor: task.monitoring ? task.monitoring.status : '-',
776
+ next: task.recommendedCommand || '-',
777
+ missing: task.missing.slice(0, 4).join(', ') + (task.missing.length > 4 ? ', ...' : ''),
778
+ }));
779
+ lines.push(renderTable(taskRows, [
780
+ { key: 'task', label: 'Task' },
781
+ { key: 'phase', label: 'Phase' },
782
+ { key: 'progress', label: 'Progress' },
783
+ { key: 'monitor', label: 'Monitor' },
784
+ { key: 'next', label: 'Next' },
785
+ { key: 'missing', label: 'Missing' },
786
+ ]));
787
+ }
788
+
789
+ if (report.errors.length) {
790
+ lines.push('');
791
+ lines.push('Errors:');
792
+ for (const error of report.errors) {
793
+ lines.push(`- ${error.source || error.root || error.project || 'unknown'}: ${error.message}`);
794
+ }
795
+ }
796
+
797
+ return `${lines.join('\n')}\n`;
798
+ }
799
+
800
+ function shouldFailOnRisk(options, report) {
801
+ return Boolean(
802
+ options.failOnRisk
803
+ && report.monitoringSummary
804
+ && (report.monitoringSummary.blockedTasks > 0 || report.monitoringSummary.staleTasks > 0),
805
+ );
806
+ }
807
+
808
+ function main(argv = process.argv.slice(2)) {
809
+ const options = parseArgs(argv);
810
+ if (options.help) {
811
+ process.stdout.write(`${getHelpText()}\n`);
812
+ return;
813
+ }
814
+
815
+ const report = buildReport(options);
816
+ if (shouldFailOnRisk(options, report)) {
817
+ process.exitCode = 1;
818
+ }
819
+ if (options.json) {
820
+ process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
821
+ return;
822
+ }
823
+ process.stdout.write(formatHumanReport(report));
824
+ }
825
+
826
+ if (require.main === module) {
827
+ try {
828
+ main();
829
+ } catch (error) {
830
+ process.stderr.write(`Error: ${error.message}\n`);
831
+ process.exitCode = 1;
832
+ }
833
+ }
834
+
835
+ module.exports = {
836
+ ARTIFACTS,
837
+ buildReport,
838
+ collectProjectEntries,
839
+ formatHumanReport,
840
+ inferTaskProgress,
841
+ inspectArtifacts,
842
+ inspectProject,
843
+ loadRegistry,
844
+ main,
845
+ monitorTask,
846
+ parseArgs,
847
+ parseProjectContext,
848
+ scanProjects,
849
+ shouldFailOnRisk,
850
+ summarizeMonitoring,
851
+ summarizeTasks,
852
+ };