mstro-app 0.4.3 → 0.4.4
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/server/cli/headless/claude-invoker-process.d.ts +11 -0
- package/dist/server/cli/headless/claude-invoker-process.d.ts.map +1 -0
- package/dist/server/cli/headless/claude-invoker-process.js +140 -0
- package/dist/server/cli/headless/claude-invoker-process.js.map +1 -0
- package/dist/server/cli/headless/claude-invoker-stall.d.ts +40 -0
- package/dist/server/cli/headless/claude-invoker-stall.d.ts.map +1 -0
- package/dist/server/cli/headless/claude-invoker-stall.js +98 -0
- package/dist/server/cli/headless/claude-invoker-stall.js.map +1 -0
- package/dist/server/cli/headless/claude-invoker-stream.d.ts +44 -0
- package/dist/server/cli/headless/claude-invoker-stream.d.ts.map +1 -0
- package/dist/server/cli/headless/claude-invoker-stream.js +276 -0
- package/dist/server/cli/headless/claude-invoker-stream.js.map +1 -0
- package/dist/server/cli/headless/claude-invoker-tools.d.ts +21 -0
- package/dist/server/cli/headless/claude-invoker-tools.d.ts.map +1 -0
- package/dist/server/cli/headless/claude-invoker-tools.js +137 -0
- package/dist/server/cli/headless/claude-invoker-tools.js.map +1 -0
- package/dist/server/cli/headless/claude-invoker.d.ts +6 -4
- package/dist/server/cli/headless/claude-invoker.d.ts.map +1 -1
- package/dist/server/cli/headless/claude-invoker.js +10 -807
- package/dist/server/cli/headless/claude-invoker.js.map +1 -1
- package/dist/server/cli/headless/haiku-assessments.d.ts +62 -0
- package/dist/server/cli/headless/haiku-assessments.d.ts.map +1 -0
- package/dist/server/cli/headless/haiku-assessments.js +281 -0
- package/dist/server/cli/headless/haiku-assessments.js.map +1 -0
- package/dist/server/cli/headless/headless-logger.d.ts +3 -2
- package/dist/server/cli/headless/headless-logger.d.ts.map +1 -1
- package/dist/server/cli/headless/headless-logger.js +28 -5
- package/dist/server/cli/headless/headless-logger.js.map +1 -1
- package/dist/server/cli/headless/native-timeout-detector.d.ts +44 -0
- package/dist/server/cli/headless/native-timeout-detector.d.ts.map +1 -0
- package/dist/server/cli/headless/native-timeout-detector.js +99 -0
- package/dist/server/cli/headless/native-timeout-detector.js.map +1 -0
- package/dist/server/cli/headless/stall-assessor.d.ts +2 -110
- package/dist/server/cli/headless/stall-assessor.d.ts.map +1 -1
- package/dist/server/cli/headless/stall-assessor.js +65 -457
- package/dist/server/cli/headless/stall-assessor.js.map +1 -1
- package/dist/server/cli/improvisation-attachments.d.ts +21 -0
- package/dist/server/cli/improvisation-attachments.d.ts.map +1 -0
- package/dist/server/cli/improvisation-attachments.js +116 -0
- package/dist/server/cli/improvisation-attachments.js.map +1 -0
- package/dist/server/cli/improvisation-retry.d.ts +52 -0
- package/dist/server/cli/improvisation-retry.d.ts.map +1 -0
- package/dist/server/cli/improvisation-retry.js +434 -0
- package/dist/server/cli/improvisation-retry.js.map +1 -0
- package/dist/server/cli/improvisation-session-manager.d.ts +10 -266
- package/dist/server/cli/improvisation-session-manager.d.ts.map +1 -1
- package/dist/server/cli/improvisation-session-manager.js +117 -1079
- package/dist/server/cli/improvisation-session-manager.js.map +1 -1
- package/dist/server/cli/improvisation-types.d.ts +86 -0
- package/dist/server/cli/improvisation-types.d.ts.map +1 -0
- package/dist/server/cli/improvisation-types.js +10 -0
- package/dist/server/cli/improvisation-types.js.map +1 -0
- package/dist/server/cli/prompt-builders.d.ts +68 -0
- package/dist/server/cli/prompt-builders.d.ts.map +1 -0
- package/dist/server/cli/prompt-builders.js +312 -0
- package/dist/server/cli/prompt-builders.js.map +1 -0
- package/dist/server/index.js +33 -212
- package/dist/server/index.js.map +1 -1
- package/dist/server/mcp/bouncer-haiku.d.ts +10 -0
- package/dist/server/mcp/bouncer-haiku.d.ts.map +1 -0
- package/dist/server/mcp/bouncer-haiku.js +152 -0
- package/dist/server/mcp/bouncer-haiku.js.map +1 -0
- package/dist/server/mcp/bouncer-integration.d.ts +3 -4
- package/dist/server/mcp/bouncer-integration.d.ts.map +1 -1
- package/dist/server/mcp/bouncer-integration.js +50 -196
- package/dist/server/mcp/bouncer-integration.js.map +1 -1
- package/dist/server/mcp/security-analysis.d.ts +38 -0
- package/dist/server/mcp/security-analysis.d.ts.map +1 -0
- package/dist/server/mcp/security-analysis.js +183 -0
- package/dist/server/mcp/security-analysis.js.map +1 -0
- package/dist/server/mcp/security-audit.d.ts +1 -1
- package/dist/server/mcp/security-audit.d.ts.map +1 -1
- package/dist/server/mcp/security-patterns.d.ts +1 -25
- package/dist/server/mcp/security-patterns.d.ts.map +1 -1
- package/dist/server/mcp/security-patterns.js +55 -260
- package/dist/server/mcp/security-patterns.js.map +1 -1
- package/dist/server/server-setup.d.ts +22 -0
- package/dist/server/server-setup.d.ts.map +1 -0
- package/dist/server/server-setup.js +101 -0
- package/dist/server/server-setup.js.map +1 -0
- package/dist/server/services/file-explorer-ops.d.ts +24 -0
- package/dist/server/services/file-explorer-ops.d.ts.map +1 -0
- package/dist/server/services/file-explorer-ops.js +211 -0
- package/dist/server/services/file-explorer-ops.js.map +1 -0
- package/dist/server/services/files.d.ts +2 -85
- package/dist/server/services/files.d.ts.map +1 -1
- package/dist/server/services/files.js +7 -427
- package/dist/server/services/files.js.map +1 -1
- package/dist/server/services/plan/composer.d.ts.map +1 -1
- package/dist/server/services/plan/composer.js +2 -1
- package/dist/server/services/plan/composer.js.map +1 -1
- package/dist/server/services/plan/executor.d.ts.map +1 -1
- package/dist/server/services/plan/executor.js +3 -1
- package/dist/server/services/plan/executor.js.map +1 -1
- package/dist/server/services/plan/parser-core.d.ts +20 -0
- package/dist/server/services/plan/parser-core.d.ts.map +1 -0
- package/dist/server/services/plan/parser-core.js +350 -0
- package/dist/server/services/plan/parser-core.js.map +1 -0
- package/dist/server/services/plan/parser-migration.d.ts +5 -0
- package/dist/server/services/plan/parser-migration.d.ts.map +1 -0
- package/dist/server/services/plan/parser-migration.js +124 -0
- package/dist/server/services/plan/parser-migration.js.map +1 -0
- package/dist/server/services/plan/parser.d.ts +0 -8
- package/dist/server/services/plan/parser.d.ts.map +1 -1
- package/dist/server/services/plan/parser.js +50 -569
- package/dist/server/services/plan/parser.js.map +1 -1
- package/dist/server/services/plan/review-gate.d.ts +2 -0
- package/dist/server/services/plan/review-gate.d.ts.map +1 -1
- package/dist/server/services/plan/review-gate.js +2 -2
- package/dist/server/services/plan/review-gate.js.map +1 -1
- package/dist/server/services/plan/types.d.ts +2 -0
- package/dist/server/services/plan/types.d.ts.map +1 -1
- package/dist/server/services/platform-credentials.d.ts +24 -0
- package/dist/server/services/platform-credentials.d.ts.map +1 -0
- package/dist/server/services/platform-credentials.js +68 -0
- package/dist/server/services/platform-credentials.js.map +1 -0
- package/dist/server/services/platform.d.ts +1 -31
- package/dist/server/services/platform.d.ts.map +1 -1
- package/dist/server/services/platform.js +10 -119
- package/dist/server/services/platform.js.map +1 -1
- package/dist/server/services/terminal/pty-manager.d.ts +7 -97
- package/dist/server/services/terminal/pty-manager.d.ts.map +1 -1
- package/dist/server/services/terminal/pty-manager.js +53 -266
- package/dist/server/services/terminal/pty-manager.js.map +1 -1
- package/dist/server/services/terminal/pty-utils.d.ts +57 -0
- package/dist/server/services/terminal/pty-utils.d.ts.map +1 -0
- package/dist/server/services/terminal/pty-utils.js +141 -0
- package/dist/server/services/terminal/pty-utils.js.map +1 -0
- package/dist/server/services/websocket/file-definition-handlers.d.ts +4 -0
- package/dist/server/services/websocket/file-definition-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/file-definition-handlers.js +153 -0
- package/dist/server/services/websocket/file-definition-handlers.js.map +1 -0
- package/dist/server/services/websocket/file-explorer-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/file-explorer-handlers.js +52 -391
- package/dist/server/services/websocket/file-explorer-handlers.js.map +1 -1
- package/dist/server/services/websocket/file-search-handlers.d.ts +5 -0
- package/dist/server/services/websocket/file-search-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/file-search-handlers.js +238 -0
- package/dist/server/services/websocket/file-search-handlers.js.map +1 -0
- package/dist/server/services/websocket/file-utils.js +3 -3
- package/dist/server/services/websocket/file-utils.js.map +1 -1
- package/dist/server/services/websocket/git-branch-handlers.d.ts +7 -0
- package/dist/server/services/websocket/git-branch-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/git-branch-handlers.js +110 -0
- package/dist/server/services/websocket/git-branch-handlers.js.map +1 -0
- package/dist/server/services/websocket/git-diff-handlers.d.ts +6 -0
- package/dist/server/services/websocket/git-diff-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/git-diff-handlers.js +123 -0
- package/dist/server/services/websocket/git-diff-handlers.js.map +1 -0
- package/dist/server/services/websocket/git-handlers.d.ts +2 -31
- package/dist/server/services/websocket/git-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/git-handlers.js +35 -541
- package/dist/server/services/websocket/git-handlers.js.map +1 -1
- package/dist/server/services/websocket/git-log-handlers.d.ts +6 -0
- package/dist/server/services/websocket/git-log-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/git-log-handlers.js +128 -0
- package/dist/server/services/websocket/git-log-handlers.js.map +1 -0
- package/dist/server/services/websocket/git-pr-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/git-pr-handlers.js +13 -53
- package/dist/server/services/websocket/git-pr-handlers.js.map +1 -1
- package/dist/server/services/websocket/git-tag-handlers.d.ts +6 -0
- package/dist/server/services/websocket/git-tag-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/git-tag-handlers.js +76 -0
- package/dist/server/services/websocket/git-tag-handlers.js.map +1 -0
- package/dist/server/services/websocket/git-utils.d.ts +43 -0
- package/dist/server/services/websocket/git-utils.d.ts.map +1 -0
- package/dist/server/services/websocket/git-utils.js +201 -0
- package/dist/server/services/websocket/git-utils.js.map +1 -0
- package/dist/server/services/websocket/handler.d.ts +2 -0
- package/dist/server/services/websocket/handler.d.ts.map +1 -1
- package/dist/server/services/websocket/handler.js +37 -126
- package/dist/server/services/websocket/handler.js.map +1 -1
- package/dist/server/services/websocket/plan-board-handlers.d.ts +11 -0
- package/dist/server/services/websocket/plan-board-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/plan-board-handlers.js +218 -0
- package/dist/server/services/websocket/plan-board-handlers.js.map +1 -0
- package/dist/server/services/websocket/plan-execution-handlers.d.ts +9 -0
- package/dist/server/services/websocket/plan-execution-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/plan-execution-handlers.js +142 -0
- package/dist/server/services/websocket/plan-execution-handlers.js.map +1 -0
- package/dist/server/services/websocket/plan-handlers.d.ts +7 -2
- package/dist/server/services/websocket/plan-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/plan-handlers.js +6 -925
- package/dist/server/services/websocket/plan-handlers.js.map +1 -1
- package/dist/server/services/websocket/plan-helpers.d.ts +19 -0
- package/dist/server/services/websocket/plan-helpers.d.ts.map +1 -0
- package/dist/server/services/websocket/plan-helpers.js +199 -0
- package/dist/server/services/websocket/plan-helpers.js.map +1 -0
- package/dist/server/services/websocket/plan-issue-handlers.d.ts +12 -0
- package/dist/server/services/websocket/plan-issue-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/plan-issue-handlers.js +162 -0
- package/dist/server/services/websocket/plan-issue-handlers.js.map +1 -0
- package/dist/server/services/websocket/plan-sprint-handlers.d.ts +7 -0
- package/dist/server/services/websocket/plan-sprint-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/plan-sprint-handlers.js +206 -0
- package/dist/server/services/websocket/plan-sprint-handlers.js.map +1 -0
- package/dist/server/services/websocket/quality-complexity.d.ts +14 -0
- package/dist/server/services/websocket/quality-complexity.d.ts.map +1 -0
- package/dist/server/services/websocket/quality-complexity.js +262 -0
- package/dist/server/services/websocket/quality-complexity.js.map +1 -0
- package/dist/server/services/websocket/quality-fix-agent.d.ts +16 -0
- package/dist/server/services/websocket/quality-fix-agent.d.ts.map +1 -0
- package/dist/server/services/websocket/quality-fix-agent.js +140 -0
- package/dist/server/services/websocket/quality-fix-agent.js.map +1 -0
- package/dist/server/services/websocket/quality-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/quality-handlers.js +34 -346
- package/dist/server/services/websocket/quality-handlers.js.map +1 -1
- package/dist/server/services/websocket/quality-linting.d.ts +9 -0
- package/dist/server/services/websocket/quality-linting.d.ts.map +1 -0
- package/dist/server/services/websocket/quality-linting.js +178 -0
- package/dist/server/services/websocket/quality-linting.js.map +1 -0
- package/dist/server/services/websocket/quality-review-agent.d.ts +19 -0
- package/dist/server/services/websocket/quality-review-agent.d.ts.map +1 -0
- package/dist/server/services/websocket/quality-review-agent.js +206 -0
- package/dist/server/services/websocket/quality-review-agent.js.map +1 -0
- package/dist/server/services/websocket/quality-service.d.ts +3 -51
- package/dist/server/services/websocket/quality-service.d.ts.map +1 -1
- package/dist/server/services/websocket/quality-service.js +9 -651
- package/dist/server/services/websocket/quality-service.js.map +1 -1
- package/dist/server/services/websocket/quality-tools.d.ts +23 -0
- package/dist/server/services/websocket/quality-tools.d.ts.map +1 -0
- package/dist/server/services/websocket/quality-tools.js +208 -0
- package/dist/server/services/websocket/quality-tools.js.map +1 -0
- package/dist/server/services/websocket/quality-types.d.ts +59 -0
- package/dist/server/services/websocket/quality-types.d.ts.map +1 -0
- package/dist/server/services/websocket/quality-types.js +101 -0
- package/dist/server/services/websocket/quality-types.js.map +1 -0
- package/dist/server/services/websocket/session-handlers.d.ts +3 -4
- package/dist/server/services/websocket/session-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/session-handlers.js +3 -378
- package/dist/server/services/websocket/session-handlers.js.map +1 -1
- package/dist/server/services/websocket/session-history.d.ts +4 -0
- package/dist/server/services/websocket/session-history.d.ts.map +1 -0
- package/dist/server/services/websocket/session-history.js +208 -0
- package/dist/server/services/websocket/session-history.js.map +1 -0
- package/dist/server/services/websocket/session-initialization.d.ts +5 -0
- package/dist/server/services/websocket/session-initialization.d.ts.map +1 -0
- package/dist/server/services/websocket/session-initialization.js +163 -0
- package/dist/server/services/websocket/session-initialization.js.map +1 -0
- package/dist/server/services/websocket/types.d.ts +12 -2
- package/dist/server/services/websocket/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/server/cli/headless/claude-invoker-process.ts +204 -0
- package/server/cli/headless/claude-invoker-stall.ts +164 -0
- package/server/cli/headless/claude-invoker-stream.ts +353 -0
- package/server/cli/headless/claude-invoker-tools.ts +187 -0
- package/server/cli/headless/claude-invoker.ts +15 -1096
- package/server/cli/headless/haiku-assessments.ts +365 -0
- package/server/cli/headless/headless-logger.ts +26 -5
- package/server/cli/headless/native-timeout-detector.ts +117 -0
- package/server/cli/headless/stall-assessor.ts +65 -618
- package/server/cli/improvisation-attachments.ts +148 -0
- package/server/cli/improvisation-retry.ts +602 -0
- package/server/cli/improvisation-session-manager.ts +140 -1349
- package/server/cli/improvisation-types.ts +98 -0
- package/server/cli/prompt-builders.ts +370 -0
- package/server/index.ts +35 -246
- package/server/mcp/bouncer-haiku.ts +182 -0
- package/server/mcp/bouncer-integration.ts +87 -248
- package/server/mcp/security-analysis.ts +217 -0
- package/server/mcp/security-audit.ts +1 -1
- package/server/mcp/security-patterns.ts +60 -283
- package/server/server-setup.ts +114 -0
- package/server/services/file-explorer-ops.ts +293 -0
- package/server/services/files.ts +20 -532
- package/server/services/plan/composer.ts +2 -1
- package/server/services/plan/executor.ts +3 -1
- package/server/services/plan/parser-core.ts +406 -0
- package/server/services/plan/parser-migration.ts +128 -0
- package/server/services/plan/parser.ts +52 -620
- package/server/services/plan/review-gate.ts +4 -2
- package/server/services/plan/types.ts +2 -0
- package/server/services/platform-credentials.ts +83 -0
- package/server/services/platform.ts +15 -141
- package/server/services/terminal/pty-manager.ts +66 -313
- package/server/services/terminal/pty-utils.ts +176 -0
- package/server/services/websocket/file-definition-handlers.ts +165 -0
- package/server/services/websocket/file-explorer-handlers.ts +37 -452
- package/server/services/websocket/file-search-handlers.ts +291 -0
- package/server/services/websocket/file-utils.ts +3 -3
- package/server/services/websocket/git-branch-handlers.ts +130 -0
- package/server/services/websocket/git-diff-handlers.ts +140 -0
- package/server/services/websocket/git-handlers.ts +40 -625
- package/server/services/websocket/git-log-handlers.ts +149 -0
- package/server/services/websocket/git-pr-handlers.ts +17 -62
- package/server/services/websocket/git-tag-handlers.ts +91 -0
- package/server/services/websocket/git-utils.ts +230 -0
- package/server/services/websocket/handler.ts +39 -126
- package/server/services/websocket/plan-board-handlers.ts +277 -0
- package/server/services/websocket/plan-execution-handlers.ts +184 -0
- package/server/services/websocket/plan-handlers.ts +8 -1114
- package/server/services/websocket/plan-helpers.ts +215 -0
- package/server/services/websocket/plan-issue-handlers.ts +204 -0
- package/server/services/websocket/plan-sprint-handlers.ts +252 -0
- package/server/services/websocket/quality-complexity.ts +294 -0
- package/server/services/websocket/quality-fix-agent.ts +181 -0
- package/server/services/websocket/quality-handlers.ts +36 -404
- package/server/services/websocket/quality-linting.ts +187 -0
- package/server/services/websocket/quality-review-agent.ts +246 -0
- package/server/services/websocket/quality-service.ts +11 -762
- package/server/services/websocket/quality-tools.ts +209 -0
- package/server/services/websocket/quality-types.ts +169 -0
- package/server/services/websocket/session-handlers.ts +5 -437
- package/server/services/websocket/session-history.ts +222 -0
- package/server/services/websocket/session-initialization.ts +209 -0
- package/server/services/websocket/types.ts +17 -0
|
@@ -1,522 +1,20 @@
|
|
|
1
1
|
// Copyright (c) 2025-present Mstro, Inc. All rights reserved.
|
|
2
2
|
// Licensed under the MIT License. See LICENSE file for details.
|
|
3
3
|
/**
|
|
4
|
-
* PPS Parser —
|
|
4
|
+
* PPS Parser — Public API for reading .pm/ (or legacy .plan/) directories.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
6
|
+
* Entity parsing lives in parser-core.ts; migration in parser-migration.ts.
|
|
7
7
|
*/
|
|
8
|
-
import {
|
|
8
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
9
9
|
import { join } from 'node:path';
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
return v.slice(1, -1);
|
|
13
|
-
}
|
|
14
|
-
return v;
|
|
15
|
-
}
|
|
16
|
-
function parseYamlValue(v) {
|
|
17
|
-
if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) {
|
|
18
|
-
return v.slice(1, -1);
|
|
19
|
-
}
|
|
20
|
-
if (v.startsWith('[') && v.endsWith(']')) {
|
|
21
|
-
return v.slice(1, -1).split(',').map(s => stripQuotes(s.trim())).filter(Boolean);
|
|
22
|
-
}
|
|
23
|
-
if (v === 'true')
|
|
24
|
-
return true;
|
|
25
|
-
if (v === 'false')
|
|
26
|
-
return false;
|
|
27
|
-
if (v === 'null' || v === '~' || v === '')
|
|
28
|
-
return null;
|
|
29
|
-
if (/^-?\d+(\.\d+)?$/.test(v))
|
|
30
|
-
return Number(v);
|
|
31
|
-
return v;
|
|
32
|
-
}
|
|
33
|
-
/** Consume indented YAML list items starting after the current index. Returns [items, newIndex]. */
|
|
34
|
-
function consumeIndentedList(lines, startIdx) {
|
|
35
|
-
const items = [];
|
|
36
|
-
let i = startIdx;
|
|
37
|
-
while (i + 1 < lines.length && /^\s+-\s/.test(lines[i + 1])) {
|
|
38
|
-
i++;
|
|
39
|
-
const item = lines[i].trim().replace(/^-\s+/, '');
|
|
40
|
-
items.push(stripQuotes(item));
|
|
41
|
-
}
|
|
42
|
-
return [items, i];
|
|
43
|
-
}
|
|
44
|
-
function parseFrontMatter(content) {
|
|
45
|
-
const match = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
46
|
-
if (!match) {
|
|
47
|
-
return { frontMatter: {}, body: content };
|
|
48
|
-
}
|
|
49
|
-
const frontMatter = {};
|
|
50
|
-
const lines = match[1].split('\n');
|
|
51
|
-
for (let i = 0; i < lines.length; i++) {
|
|
52
|
-
const trimmed = lines[i].trim();
|
|
53
|
-
if (!trimmed || trimmed.startsWith('#'))
|
|
54
|
-
continue;
|
|
55
|
-
const colonIdx = trimmed.indexOf(':');
|
|
56
|
-
if (colonIdx === -1)
|
|
57
|
-
continue;
|
|
58
|
-
const key = trimmed.slice(0, colonIdx).trim();
|
|
59
|
-
const rawValue = trimmed.slice(colonIdx + 1).trim();
|
|
60
|
-
if (!rawValue) {
|
|
61
|
-
const [items, newIdx] = consumeIndentedList(lines, i);
|
|
62
|
-
i = newIdx;
|
|
63
|
-
frontMatter[key] = items.length > 0 ? items : null;
|
|
64
|
-
}
|
|
65
|
-
else {
|
|
66
|
-
frontMatter[key] = parseYamlValue(rawValue);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
return { frontMatter, body: match[2] };
|
|
70
|
-
}
|
|
71
|
-
// ============================================================================
|
|
72
|
-
// Section Extraction
|
|
73
|
-
// ============================================================================
|
|
74
|
-
function extractSections(body) {
|
|
75
|
-
const sections = new Map();
|
|
76
|
-
const lines = body.split('\n');
|
|
77
|
-
let currentSection = '';
|
|
78
|
-
let currentContent = [];
|
|
79
|
-
for (const line of lines) {
|
|
80
|
-
if (line.startsWith('## ')) {
|
|
81
|
-
if (currentSection) {
|
|
82
|
-
sections.set(currentSection, currentContent.join('\n').trim());
|
|
83
|
-
}
|
|
84
|
-
currentSection = line.slice(3).trim();
|
|
85
|
-
currentContent = [];
|
|
86
|
-
}
|
|
87
|
-
else {
|
|
88
|
-
currentContent.push(line);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
if (currentSection) {
|
|
92
|
-
sections.set(currentSection, currentContent.join('\n').trim());
|
|
93
|
-
}
|
|
94
|
-
return sections;
|
|
95
|
-
}
|
|
96
|
-
function parseCheckboxes(content) {
|
|
97
|
-
const items = [];
|
|
98
|
-
for (const line of content.split('\n')) {
|
|
99
|
-
const match = line.match(/^[-*]\s+\[([ xX])\]\s+(.+)$/);
|
|
100
|
-
if (match) {
|
|
101
|
-
items.push({ text: match[2].trim(), checked: match[1] !== ' ' });
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
return items;
|
|
105
|
-
}
|
|
106
|
-
function parseListItems(content) {
|
|
107
|
-
const items = [];
|
|
108
|
-
for (const line of content.split('\n')) {
|
|
109
|
-
const match = line.match(/^[-*]\s+(.+)$/);
|
|
110
|
-
if (match)
|
|
111
|
-
items.push(match[1].trim());
|
|
112
|
-
}
|
|
113
|
-
return items;
|
|
114
|
-
}
|
|
115
|
-
function parseIssueSummaries(content) {
|
|
116
|
-
const summaries = [];
|
|
117
|
-
for (const line of content.split('\n')) {
|
|
118
|
-
// Match: 1. [IS-003](backlog/IS-003.md) — Title (P1)
|
|
119
|
-
const match = line.match(/\d+\.\s+\[([^\]]+)\]\(([^)]+)\)\s*[—–-]\s*(.+?)(?:\s*\((\w+)\))?\s*$/);
|
|
120
|
-
if (match) {
|
|
121
|
-
summaries.push({
|
|
122
|
-
id: match[1],
|
|
123
|
-
path: match[2],
|
|
124
|
-
title: match[3].trim(),
|
|
125
|
-
priority: match[4] || '',
|
|
126
|
-
});
|
|
127
|
-
continue;
|
|
128
|
-
}
|
|
129
|
-
// Match: - [IS-001](backlog/IS-001.md) — Title
|
|
130
|
-
const match2 = line.match(/^[-*]\s+\[([^\]]+)\]\(([^)]+)\)\s*[—–-]\s*(.+?)(?:\s*[→→]\s*blocked by\s+\[([^\]]+)\])?\s*$/i);
|
|
131
|
-
if (match2) {
|
|
132
|
-
summaries.push({
|
|
133
|
-
id: match2[1],
|
|
134
|
-
path: match2[2],
|
|
135
|
-
title: match2[3].trim(),
|
|
136
|
-
priority: '',
|
|
137
|
-
blockedBy: match2[4] || undefined,
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
return summaries;
|
|
142
|
-
}
|
|
143
|
-
function parseCompletedSummaries(content) {
|
|
144
|
-
const summaries = [];
|
|
145
|
-
for (const line of content.split('\n')) {
|
|
146
|
-
const match = line.match(/^[-*]\s+\[([^\]]+)\]\(([^)]+)\)\s*[—–-]\s*(.+?)(?:\s*✓)?\s*$/);
|
|
147
|
-
if (match) {
|
|
148
|
-
summaries.push({
|
|
149
|
-
id: match[1],
|
|
150
|
-
path: match[2],
|
|
151
|
-
title: match[3].trim(),
|
|
152
|
-
priority: '',
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
return summaries;
|
|
157
|
-
}
|
|
158
|
-
// ============================================================================
|
|
159
|
-
// Entity Parsers
|
|
160
|
-
// ============================================================================
|
|
161
|
-
function parseWorkflows(section) {
|
|
162
|
-
if (!section)
|
|
163
|
-
return [];
|
|
164
|
-
const workflows = [];
|
|
165
|
-
for (const line of section.split('\n')) {
|
|
166
|
-
const match = line.match(/\|\s*(\w+)\s*\|\s*(\w+)\s*\|\s*(.+?)\s*\|/);
|
|
167
|
-
if (match && match[1] !== 'Status') {
|
|
168
|
-
workflows.push({
|
|
169
|
-
status: match[1],
|
|
170
|
-
category: match[2],
|
|
171
|
-
description: match[3].trim(),
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
return workflows;
|
|
176
|
-
}
|
|
177
|
-
function parseTeams(section) {
|
|
178
|
-
if (!section)
|
|
179
|
-
return [];
|
|
180
|
-
const teams = [];
|
|
181
|
-
for (const line of section.split('\n')) {
|
|
182
|
-
const match = line.match(/^[-*]\s+(\w+)(?:\s*[—–-]\s*(.+))?$/);
|
|
183
|
-
if (match)
|
|
184
|
-
teams.push({ name: match[1], description: match[2]?.trim() });
|
|
185
|
-
}
|
|
186
|
-
return teams;
|
|
187
|
-
}
|
|
188
|
-
function parseProjectConfig(content) {
|
|
189
|
-
const { frontMatter, body } = parseFrontMatter(content);
|
|
190
|
-
const sections = extractSections(body);
|
|
191
|
-
const idPrefixes = {};
|
|
192
|
-
const rawPrefixes = frontMatter.id_prefixes;
|
|
193
|
-
if (rawPrefixes && typeof rawPrefixes === 'object') {
|
|
194
|
-
Object.assign(idPrefixes, rawPrefixes);
|
|
195
|
-
}
|
|
196
|
-
return {
|
|
197
|
-
name: String(frontMatter.name || ''),
|
|
198
|
-
id: String(frontMatter.id || ''),
|
|
199
|
-
created: String(frontMatter.created || ''),
|
|
200
|
-
status: frontMatter.status || 'active',
|
|
201
|
-
estimation: frontMatter.estimation || 'none',
|
|
202
|
-
idPrefixes,
|
|
203
|
-
workflows: parseWorkflows(sections.get('Workflows')),
|
|
204
|
-
labels: (Array.isArray(frontMatter.labels) ? frontMatter.labels : []),
|
|
205
|
-
teams: parseTeams(sections.get('Teams')),
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
function parseProjectState(content) {
|
|
209
|
-
const { frontMatter, body } = parseFrontMatter(content);
|
|
210
|
-
const sections = extractSections(body);
|
|
211
|
-
return {
|
|
212
|
-
project: String(frontMatter.project || ''),
|
|
213
|
-
currentSprint: frontMatter.current_sprint || null,
|
|
214
|
-
activeMilestone: frontMatter.active_milestone || null,
|
|
215
|
-
paused: frontMatter.paused === true,
|
|
216
|
-
lastSession: frontMatter.last_session || null,
|
|
217
|
-
readyToWork: parseIssueSummaries(sections.get('Ready to Work') || ''),
|
|
218
|
-
inProgress: parseIssueSummaries(sections.get('In Progress') || ''),
|
|
219
|
-
blocked: parseIssueSummaries(sections.get('Blocked') || ''),
|
|
220
|
-
recentlyCompleted: parseCompletedSummaries(sections.get('Recently Completed') || ''),
|
|
221
|
-
warnings: parseListItems(sections.get('Warnings') || ''),
|
|
222
|
-
};
|
|
223
|
-
}
|
|
224
|
-
function toStringArray(val) {
|
|
225
|
-
return Array.isArray(val) ? val.map(String) : [];
|
|
226
|
-
}
|
|
227
|
-
function optionalString(val) {
|
|
228
|
-
if (val == null)
|
|
229
|
-
return null;
|
|
230
|
-
const s = String(val);
|
|
231
|
-
return s === '' ? null : s;
|
|
232
|
-
}
|
|
233
|
-
function parseIssue(content, filePath) {
|
|
234
|
-
const { frontMatter: fm, body } = parseFrontMatter(content);
|
|
235
|
-
const sections = extractSections(body);
|
|
236
|
-
return {
|
|
237
|
-
id: String(fm.id || ''),
|
|
238
|
-
title: String(fm.title || ''),
|
|
239
|
-
type: fm.type || 'issue',
|
|
240
|
-
status: String(fm.status || 'backlog'),
|
|
241
|
-
priority: String(fm.priority || 'P2'),
|
|
242
|
-
estimate: fm.estimate != null ? fm.estimate : null,
|
|
243
|
-
labels: toStringArray(fm.labels),
|
|
244
|
-
epic: optionalString(fm.epic),
|
|
245
|
-
sprint: optionalString(fm.sprint),
|
|
246
|
-
milestone: optionalString(fm.milestone),
|
|
247
|
-
assigned: optionalString(fm.assigned),
|
|
248
|
-
created: String(fm.created || ''),
|
|
249
|
-
updated: optionalString(fm.updated),
|
|
250
|
-
due: optionalString(fm.due),
|
|
251
|
-
blockedBy: toStringArray(fm.blocked_by),
|
|
252
|
-
blocks: toStringArray(fm.blocks),
|
|
253
|
-
relatesTo: toStringArray(fm.relates_to),
|
|
254
|
-
children: toStringArray(fm.children),
|
|
255
|
-
progress: optionalString(fm.progress),
|
|
256
|
-
description: sections.get('Description') || '',
|
|
257
|
-
acceptanceCriteria: parseCheckboxes(sections.get('Acceptance Criteria') || ''),
|
|
258
|
-
technicalNotes: sections.get('Technical Notes') || null,
|
|
259
|
-
filesToModify: parseListItems(sections.get('Files to Modify') || ''),
|
|
260
|
-
activity: parseListItems(sections.get('Activity') || ''),
|
|
261
|
-
reviewGate: (['none', 'auto', 'required'].includes(String(fm.review_gate)) ? String(fm.review_gate) : 'auto'),
|
|
262
|
-
outputFile: optionalString(fm.output_file),
|
|
263
|
-
body,
|
|
264
|
-
path: filePath,
|
|
265
|
-
};
|
|
266
|
-
}
|
|
267
|
-
function parseSprintIssues(section) {
|
|
268
|
-
if (!section)
|
|
269
|
-
return [];
|
|
270
|
-
const issues = [];
|
|
271
|
-
for (const line of section.split('\n')) {
|
|
272
|
-
const match = line.match(/\|\s*\[([^\]]+)\]\(([^)]+)\)\s*\|\s*(.+?)\s*\|\s*(\S+)\s*\|\s*(\S+)\s*\|/);
|
|
273
|
-
if (match) {
|
|
274
|
-
issues.push({
|
|
275
|
-
id: match[1],
|
|
276
|
-
path: match[2],
|
|
277
|
-
title: match[3].trim(),
|
|
278
|
-
points: /^\d+$/.test(match[4]) ? Number(match[4]) : match[4],
|
|
279
|
-
status: match[5],
|
|
280
|
-
});
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
return issues;
|
|
284
|
-
}
|
|
285
|
-
function optionalNumber(val) {
|
|
286
|
-
return val != null ? Number(val) : null;
|
|
287
|
-
}
|
|
288
|
-
function parseSprint(content, filePath) {
|
|
289
|
-
const { frontMatter: fm, body } = parseFrontMatter(content);
|
|
290
|
-
const sections = extractSections(body);
|
|
291
|
-
// Table-based parsing (markdown links in table rows)
|
|
292
|
-
let issues = parseSprintIssues(sections.get('Issues'));
|
|
293
|
-
// Fallback: front matter issues array (e.g., ["backlog/IS-001.md", ...])
|
|
294
|
-
if (issues.length === 0 && Array.isArray(fm.issues)) {
|
|
295
|
-
issues = fm.issues.map(path => {
|
|
296
|
-
const id = path.replace(/^backlog\//, '').replace(/\.md$/, '');
|
|
297
|
-
return { id, path, title: '', points: null, status: '' };
|
|
298
|
-
});
|
|
299
|
-
}
|
|
300
|
-
// Parse execution_summary if present (JSON object in front matter)
|
|
301
|
-
let executionSummary = null;
|
|
302
|
-
if (fm.execution_summary && typeof fm.execution_summary === 'object') {
|
|
303
|
-
const es = fm.execution_summary;
|
|
304
|
-
executionSummary = {
|
|
305
|
-
totalIssues: Number(es.total_issues ?? 0),
|
|
306
|
-
completedIssues: Number(es.completed_issues ?? 0),
|
|
307
|
-
failedIssues: Number(es.failed_issues ?? 0),
|
|
308
|
-
totalDuration: Number(es.total_duration ?? 0),
|
|
309
|
-
waves: Number(es.waves ?? 0),
|
|
310
|
-
};
|
|
311
|
-
}
|
|
312
|
-
return {
|
|
313
|
-
id: String(fm.id || ''),
|
|
314
|
-
title: String(fm.title || ''),
|
|
315
|
-
status: fm.status || 'planned',
|
|
316
|
-
start: String(fm.start || fm.start_date || ''),
|
|
317
|
-
end: String(fm.end || fm.end_date || ''),
|
|
318
|
-
goal: String(fm.goal || sections.get('Goal') || sections.get('Sprint Goal') || ''),
|
|
319
|
-
capacity: optionalNumber(fm.capacity),
|
|
320
|
-
committed: optionalNumber(fm.committed),
|
|
321
|
-
completed: optionalNumber(fm.completed),
|
|
322
|
-
issues,
|
|
323
|
-
path: filePath,
|
|
324
|
-
completedAt: optionalString(fm.completed_at),
|
|
325
|
-
executionSummary,
|
|
326
|
-
};
|
|
327
|
-
}
|
|
328
|
-
function parseMilestone(content, filePath) {
|
|
329
|
-
const { frontMatter, body } = parseFrontMatter(content);
|
|
330
|
-
const sections = extractSections(body);
|
|
331
|
-
const epics = [];
|
|
332
|
-
const epicSection = sections.get('Epics');
|
|
333
|
-
if (epicSection) {
|
|
334
|
-
for (const line of epicSection.split('\n')) {
|
|
335
|
-
const match = line.match(/\|\s*\[([^\]]+)\]\(([^)]+)\)\s*\|\s*(.+?)\s*\|\s*(\S+)\s*\|/);
|
|
336
|
-
if (match) {
|
|
337
|
-
epics.push({
|
|
338
|
-
id: match[1],
|
|
339
|
-
path: match[2],
|
|
340
|
-
title: match[3].trim(),
|
|
341
|
-
progress: match[4],
|
|
342
|
-
});
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
return {
|
|
347
|
-
id: String(frontMatter.id || ''),
|
|
348
|
-
title: String(frontMatter.title || ''),
|
|
349
|
-
status: frontMatter.status || 'planned',
|
|
350
|
-
targetDate: frontMatter.target_date || null,
|
|
351
|
-
progress: frontMatter.progress || null,
|
|
352
|
-
definition: sections.get('Definition of Done') || '',
|
|
353
|
-
epics,
|
|
354
|
-
path: filePath,
|
|
355
|
-
};
|
|
356
|
-
}
|
|
10
|
+
import { parseBoard, parseIssue, parseMilestone, parseProjectConfig, parseProjectState, parseSprint, parseWorkspace } from './parser-core.js';
|
|
11
|
+
import { isLegacyFormat, migrateToBoards } from './parser-migration.js';
|
|
357
12
|
// ============================================================================
|
|
358
|
-
//
|
|
13
|
+
// Directory Resolution
|
|
359
14
|
// ============================================================================
|
|
360
|
-
function parseBoard(content, filePath) {
|
|
361
|
-
const { frontMatter: fm, body } = parseFrontMatter(content);
|
|
362
|
-
const sections = extractSections(body);
|
|
363
|
-
let executionSummary = null;
|
|
364
|
-
if (fm.execution_summary && typeof fm.execution_summary === 'object') {
|
|
365
|
-
const es = fm.execution_summary;
|
|
366
|
-
executionSummary = {
|
|
367
|
-
totalIssues: Number(es.total_issues ?? 0),
|
|
368
|
-
completedIssues: Number(es.completed_issues ?? 0),
|
|
369
|
-
failedIssues: Number(es.failed_issues ?? 0),
|
|
370
|
-
totalDuration: Number(es.total_duration ?? 0),
|
|
371
|
-
waves: Number(es.waves ?? 0),
|
|
372
|
-
};
|
|
373
|
-
}
|
|
374
|
-
return {
|
|
375
|
-
id: String(fm.id || ''),
|
|
376
|
-
title: String(fm.title || ''),
|
|
377
|
-
status: fm.status || 'draft',
|
|
378
|
-
created: String(fm.created || ''),
|
|
379
|
-
completedAt: optionalString(fm.completed_at),
|
|
380
|
-
goal: String(fm.goal || sections.get('Goal') || ''),
|
|
381
|
-
executionSummary,
|
|
382
|
-
path: filePath,
|
|
383
|
-
};
|
|
384
|
-
}
|
|
385
|
-
function parseWorkspace(content) {
|
|
386
|
-
try {
|
|
387
|
-
const parsed = JSON.parse(content);
|
|
388
|
-
return {
|
|
389
|
-
activeBoardId: typeof parsed.activeBoardId === 'string' ? parsed.activeBoardId : null,
|
|
390
|
-
boardOrder: Array.isArray(parsed.boardOrder) ? parsed.boardOrder.map(String) : [],
|
|
391
|
-
};
|
|
392
|
-
}
|
|
393
|
-
catch {
|
|
394
|
-
return { activeBoardId: null, boardOrder: [] };
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
/** Check whether a .pm/ directory uses the board-centric format (has boards/ subdirectory). */
|
|
398
15
|
export function isBoardCentricFormat(pmDir) {
|
|
399
16
|
return existsSync(join(pmDir, 'boards'));
|
|
400
17
|
}
|
|
401
|
-
/** Check whether a .pm/ directory uses the legacy flat format (has backlog/ at root, no boards/). */
|
|
402
|
-
function isLegacyFormat(pmDir) {
|
|
403
|
-
return existsSync(join(pmDir, 'backlog')) && !existsSync(join(pmDir, 'boards'));
|
|
404
|
-
}
|
|
405
|
-
// ============================================================================
|
|
406
|
-
// Legacy → Board Migration
|
|
407
|
-
// ============================================================================
|
|
408
|
-
/** Move all files from a legacy directory into a board subdirectory and remove the source. */
|
|
409
|
-
function moveLegacyDir(srcDir, destDir) {
|
|
410
|
-
if (!existsSync(srcDir))
|
|
411
|
-
return;
|
|
412
|
-
for (const file of readdirSync(srcDir)) {
|
|
413
|
-
renameSync(join(srcDir, file), join(destDir, file));
|
|
414
|
-
}
|
|
415
|
-
rmSync(srcDir, { recursive: true });
|
|
416
|
-
}
|
|
417
|
-
/** Move a single file if it exists. */
|
|
418
|
-
function moveLegacyFile(src, dest) {
|
|
419
|
-
if (existsSync(src))
|
|
420
|
-
renameSync(src, dest);
|
|
421
|
-
}
|
|
422
|
-
/** Copy review files from sprint sandbox directories into the board reviews dir. */
|
|
423
|
-
function copySprintReviews(sprintsDir, boardReviewsDir) {
|
|
424
|
-
for (const entry of readdirSync(sprintsDir)) {
|
|
425
|
-
if (entry.endsWith('.md'))
|
|
426
|
-
continue;
|
|
427
|
-
const reviewsDir = join(sprintsDir, entry, 'reviews');
|
|
428
|
-
if (!existsSync(reviewsDir))
|
|
429
|
-
continue;
|
|
430
|
-
for (const reviewFile of readdirSync(reviewsDir)) {
|
|
431
|
-
cpSync(join(reviewsDir, reviewFile), join(boardReviewsDir, reviewFile));
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
/** Find and return the goal from the active sprint .md file. */
|
|
436
|
-
function extractActiveSprintGoal(sprintsDir) {
|
|
437
|
-
for (const entry of readdirSync(sprintsDir).filter(e => e.endsWith('.md'))) {
|
|
438
|
-
const content = readFileIfExists(join(sprintsDir, entry));
|
|
439
|
-
if (!content)
|
|
440
|
-
continue;
|
|
441
|
-
const fm = parseFrontMatter(content).frontMatter;
|
|
442
|
-
if (fm.status === 'active')
|
|
443
|
-
return String(fm.goal || '');
|
|
444
|
-
}
|
|
445
|
-
return '';
|
|
446
|
-
}
|
|
447
|
-
/** Migrate sprint reviews and extract the active sprint's goal. */
|
|
448
|
-
function migrateLegacySprints(sprintsDir, boardReviewsDir) {
|
|
449
|
-
if (!existsSync(sprintsDir))
|
|
450
|
-
return '';
|
|
451
|
-
copySprintReviews(sprintsDir, boardReviewsDir);
|
|
452
|
-
const goal = extractActiveSprintGoal(sprintsDir);
|
|
453
|
-
rmSync(sprintsDir, { recursive: true });
|
|
454
|
-
return goal;
|
|
455
|
-
}
|
|
456
|
-
/** Clean up migrated issues: remove sprint fields, detect active issues. */
|
|
457
|
-
function cleanupMigratedIssues(boardBacklogDir) {
|
|
458
|
-
if (!existsSync(boardBacklogDir))
|
|
459
|
-
return false;
|
|
460
|
-
let hasActive = false;
|
|
461
|
-
for (const file of readdirSync(boardBacklogDir).filter(f => f.endsWith('.md'))) {
|
|
462
|
-
const content = readFileIfExists(join(boardBacklogDir, file));
|
|
463
|
-
if (!content)
|
|
464
|
-
continue;
|
|
465
|
-
if (content.match(/^status:\s*(in_progress|in_review|todo)/m))
|
|
466
|
-
hasActive = true;
|
|
467
|
-
if (content.match(/^sprint:\s*.+$/m)) {
|
|
468
|
-
writeFileSync(join(boardBacklogDir, file), content.replace(/^sprint:\s*.+\n?/m, ''), 'utf-8');
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
return hasActive;
|
|
472
|
-
}
|
|
473
|
-
/** Write the board metadata files (board.md, workspace.json, STATE.md, progress.md). */
|
|
474
|
-
function writeBoardMetadata(pmDir, boardDir, boardId, sprintGoal, hasActive) {
|
|
475
|
-
const today = new Date().toISOString().slice(0, 10);
|
|
476
|
-
const boardMd = [
|
|
477
|
-
'---', `id: ${boardId}`, 'title: "Board 1"',
|
|
478
|
-
`status: ${hasActive ? 'active' : 'draft'}`, `created: "${today}"`,
|
|
479
|
-
'completed_at: null', `goal: "${sprintGoal.replace(/"/g, '\\"')}"`,
|
|
480
|
-
'---', '', '# Board 1', '',
|
|
481
|
-
sprintGoal ? `## Goal\n${sprintGoal}\n` : '',
|
|
482
|
-
].join('\n');
|
|
483
|
-
writeFileSync(join(boardDir, 'board.md'), boardMd, 'utf-8');
|
|
484
|
-
const workspace = { activeBoardId: boardId, boardOrder: [boardId] };
|
|
485
|
-
writeFileSync(join(pmDir, 'workspace.json'), JSON.stringify(workspace, null, 2), 'utf-8');
|
|
486
|
-
if (!existsSync(join(boardDir, 'STATE.md'))) {
|
|
487
|
-
writeFileSync(join(boardDir, 'STATE.md'), [
|
|
488
|
-
'---', 'project: ../../project.md', 'board: board.md', 'paused: false', '---', '',
|
|
489
|
-
'# Board State', '', '## Ready to Work', '', '## In Progress', '',
|
|
490
|
-
'## Blocked', '', '## Recently Completed', '', '## Warnings', '',
|
|
491
|
-
].join('\n'), 'utf-8');
|
|
492
|
-
}
|
|
493
|
-
if (!existsSync(join(boardDir, 'progress.md'))) {
|
|
494
|
-
writeFileSync(join(boardDir, 'progress.md'), '# Board Progress\n', 'utf-8');
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
/**
|
|
498
|
-
* Migrate a legacy flat .pm/ directory to board-centric format.
|
|
499
|
-
* Creates BOARD-001 from the existing backlog, state, outputs, and reviews.
|
|
500
|
-
*/
|
|
501
|
-
function migrateToBoards(pmDir) {
|
|
502
|
-
const boardId = 'BOARD-001';
|
|
503
|
-
const boardDir = join(pmDir, 'boards', boardId);
|
|
504
|
-
mkdirSync(boardDir, { recursive: true });
|
|
505
|
-
mkdirSync(join(boardDir, 'backlog'), { recursive: true });
|
|
506
|
-
mkdirSync(join(boardDir, 'out'), { recursive: true });
|
|
507
|
-
mkdirSync(join(boardDir, 'reviews'), { recursive: true });
|
|
508
|
-
moveLegacyDir(join(pmDir, 'backlog'), join(boardDir, 'backlog'));
|
|
509
|
-
moveLegacyFile(join(pmDir, 'STATE.md'), join(boardDir, 'STATE.md'));
|
|
510
|
-
moveLegacyDir(join(pmDir, 'out'), join(boardDir, 'out'));
|
|
511
|
-
moveLegacyFile(join(pmDir, 'progress.md'), join(boardDir, 'progress.md'));
|
|
512
|
-
const sprintGoal = migrateLegacySprints(join(pmDir, 'sprints'), join(boardDir, 'reviews'));
|
|
513
|
-
const hasActive = cleanupMigratedIssues(join(boardDir, 'backlog'));
|
|
514
|
-
writeBoardMetadata(pmDir, boardDir, boardId, sprintGoal, hasActive);
|
|
515
|
-
}
|
|
516
|
-
// ============================================================================
|
|
517
|
-
// Directory Parser
|
|
518
|
-
// ============================================================================
|
|
519
|
-
/** Resolve the PM directory — prefers .mstro/pm/, falls back to legacy .pm/ and .plan/ */
|
|
520
18
|
export function resolvePmDir(workingDir) {
|
|
521
19
|
const mstroPmDir = join(workingDir, '.mstro', 'pm');
|
|
522
20
|
if (existsSync(mstroPmDir))
|
|
@@ -529,13 +27,15 @@ export function resolvePmDir(workingDir) {
|
|
|
529
27
|
return legacyPlanDir;
|
|
530
28
|
return null;
|
|
531
29
|
}
|
|
532
|
-
/** Default PM directory path for new projects */
|
|
533
30
|
export function defaultPmDir(workingDir) {
|
|
534
31
|
return join(workingDir, '.mstro', 'pm');
|
|
535
32
|
}
|
|
536
33
|
export function planDirExists(workingDir) {
|
|
537
34
|
return resolvePmDir(workingDir) !== null;
|
|
538
35
|
}
|
|
36
|
+
// ============================================================================
|
|
37
|
+
// File Utilities
|
|
38
|
+
// ============================================================================
|
|
539
39
|
function readFileIfExists(path) {
|
|
540
40
|
try {
|
|
541
41
|
if (existsSync(path))
|
|
@@ -550,15 +50,34 @@ function readMdFilesInDir(dirPath) {
|
|
|
550
50
|
try {
|
|
551
51
|
return readdirSync(dirPath)
|
|
552
52
|
.filter(f => f.endsWith('.md'))
|
|
553
|
-
.map(name => {
|
|
554
|
-
const content = readFileSync(join(dirPath, name), 'utf-8');
|
|
555
|
-
return { name, content };
|
|
556
|
-
});
|
|
53
|
+
.map(name => ({ name, content: readFileSync(join(dirPath, name), 'utf-8') }));
|
|
557
54
|
}
|
|
558
55
|
catch {
|
|
559
56
|
return [];
|
|
560
57
|
}
|
|
561
58
|
}
|
|
59
|
+
function listDirFiles(dirPath, ext) {
|
|
60
|
+
if (!existsSync(dirPath))
|
|
61
|
+
return [];
|
|
62
|
+
try {
|
|
63
|
+
return readdirSync(dirPath).filter(f => f.endsWith(ext));
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function readReviewResults(reviewsDir) {
|
|
70
|
+
const results = [];
|
|
71
|
+
for (const f of listDirFiles(reviewsDir, '.json')) {
|
|
72
|
+
const content = readFileIfExists(join(reviewsDir, f));
|
|
73
|
+
if (content)
|
|
74
|
+
results.push(JSON.parse(content));
|
|
75
|
+
}
|
|
76
|
+
return results;
|
|
77
|
+
}
|
|
78
|
+
// ============================================================================
|
|
79
|
+
// Defaults
|
|
80
|
+
// ============================================================================
|
|
562
81
|
const defaultProject = {
|
|
563
82
|
name: '', id: '', created: '', status: 'active', estimation: 'none',
|
|
564
83
|
idPrefixes: {}, workflows: [], labels: [], teams: [],
|
|
@@ -568,7 +87,9 @@ const defaultState = {
|
|
|
568
87
|
lastSession: null, readyToWork: [], inProgress: [], blocked: [],
|
|
569
88
|
recentlyCompleted: [], warnings: [],
|
|
570
89
|
};
|
|
571
|
-
|
|
90
|
+
// ============================================================================
|
|
91
|
+
// Board & Plan Directory Parsing
|
|
92
|
+
// ============================================================================
|
|
572
93
|
export function parseBoardDirectory(pmDir, boardId) {
|
|
573
94
|
const boardDir = join(pmDir, 'boards', boardId);
|
|
574
95
|
if (!existsSync(boardDir))
|
|
@@ -583,7 +104,6 @@ export function parseBoardDirectory(pmDir, boardId) {
|
|
|
583
104
|
const boardPrefix = `boards/${boardId}/`;
|
|
584
105
|
const issues = issueFiles.map(f => {
|
|
585
106
|
const issue = parseIssue(f.content, `${boardPrefix}backlog/${f.name}`);
|
|
586
|
-
// Normalize blocked_by/blocks to full board-relative paths so dependency resolver matches
|
|
587
107
|
issue.blockedBy = issue.blockedBy.map(bp => bp.startsWith('boards/') ? bp : `${boardPrefix}${bp}`);
|
|
588
108
|
issue.blocks = issue.blocks.map(bp => bp.startsWith('boards/') ? bp : `${boardPrefix}${bp}`);
|
|
589
109
|
if (issue.epic && !issue.epic.startsWith('boards/'))
|
|
@@ -592,7 +112,6 @@ export function parseBoardDirectory(pmDir, boardId) {
|
|
|
592
112
|
});
|
|
593
113
|
return { board, state, issues };
|
|
594
114
|
}
|
|
595
|
-
/** Parse all boards from the boards/ directory and resolve the active board. */
|
|
596
115
|
function parseBoardCentricState(planDir) {
|
|
597
116
|
const workspaceContent = readFileIfExists(join(planDir, 'workspace.json'));
|
|
598
117
|
const workspace = workspaceContent ? parseWorkspace(workspaceContent) : { activeBoardId: null, boardOrder: [] };
|
|
@@ -634,12 +153,14 @@ export function parsePlanDirectory(workingDir) {
|
|
|
634
153
|
sprints: [], milestones,
|
|
635
154
|
};
|
|
636
155
|
}
|
|
156
|
+
// ============================================================================
|
|
157
|
+
// Single Entity Parsers
|
|
158
|
+
// ============================================================================
|
|
637
159
|
export function parseSingleIssue(workingDir, issuePath) {
|
|
638
160
|
const pmDir = resolvePmDir(workingDir);
|
|
639
161
|
if (!pmDir)
|
|
640
162
|
return null;
|
|
641
|
-
const
|
|
642
|
-
const content = readFileIfExists(fullPath);
|
|
163
|
+
const content = readFileIfExists(join(pmDir, issuePath));
|
|
643
164
|
if (!content)
|
|
644
165
|
return null;
|
|
645
166
|
return parseIssue(content, issuePath);
|
|
@@ -648,8 +169,7 @@ export function parseSingleSprint(workingDir, sprintPath) {
|
|
|
648
169
|
const pmDir = resolvePmDir(workingDir);
|
|
649
170
|
if (!pmDir)
|
|
650
171
|
return null;
|
|
651
|
-
const
|
|
652
|
-
const content = readFileIfExists(fullPath);
|
|
172
|
+
const content = readFileIfExists(join(pmDir, sprintPath));
|
|
653
173
|
if (!content)
|
|
654
174
|
return null;
|
|
655
175
|
return parseSprint(content, sprintPath);
|
|
@@ -658,13 +178,14 @@ export function parseSingleMilestone(workingDir, milestonePath) {
|
|
|
658
178
|
const pmDir = resolvePmDir(workingDir);
|
|
659
179
|
if (!pmDir)
|
|
660
180
|
return null;
|
|
661
|
-
const
|
|
662
|
-
const content = readFileIfExists(fullPath);
|
|
181
|
+
const content = readFileIfExists(join(pmDir, milestonePath));
|
|
663
182
|
if (!content)
|
|
664
183
|
return null;
|
|
665
184
|
return parseMilestone(content, milestonePath);
|
|
666
185
|
}
|
|
667
|
-
|
|
186
|
+
// ============================================================================
|
|
187
|
+
// ID Generation
|
|
188
|
+
// ============================================================================
|
|
668
189
|
export function getNextId(issues, prefix) {
|
|
669
190
|
const escaped = prefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
670
191
|
const pattern = new RegExp(`^${escaped}-(\\d+)$`);
|
|
@@ -679,7 +200,6 @@ export function getNextId(issues, prefix) {
|
|
|
679
200
|
}
|
|
680
201
|
return `${prefix}-${String(max + 1).padStart(3, '0')}`;
|
|
681
202
|
}
|
|
682
|
-
/** Compute the next available board ID (e.g., "BOARD-003") */
|
|
683
203
|
export function getNextBoardId(boards) {
|
|
684
204
|
let max = 0;
|
|
685
205
|
for (const board of boards) {
|
|
@@ -692,7 +212,6 @@ export function getNextBoardId(boards) {
|
|
|
692
212
|
}
|
|
693
213
|
return `BOARD-${String(max + 1).padStart(3, '0')}`;
|
|
694
214
|
}
|
|
695
|
-
/** Compute the next available board number for display title (e.g., "Board 3") */
|
|
696
215
|
export function getNextBoardNumber(boards) {
|
|
697
216
|
let max = 0;
|
|
698
217
|
for (const board of boards) {
|
|
@@ -705,7 +224,6 @@ export function getNextBoardNumber(boards) {
|
|
|
705
224
|
}
|
|
706
225
|
return max + 1;
|
|
707
226
|
}
|
|
708
|
-
/** Parse board artifacts from boards/BOARD-N/ directory. */
|
|
709
227
|
export function parseBoardArtifacts(workingDir, boardId) {
|
|
710
228
|
const pmDir = resolvePmDir(workingDir);
|
|
711
229
|
if (!pmDir)
|
|
@@ -714,28 +232,10 @@ export function parseBoardArtifacts(workingDir, boardId) {
|
|
|
714
232
|
if (!existsSync(boardDir))
|
|
715
233
|
return null;
|
|
716
234
|
const progressLog = readFileIfExists(join(boardDir, 'progress.md')) ?? '';
|
|
717
|
-
const
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
outputFiles = readdirSync(outDir).filter(f => f.endsWith('.md'));
|
|
722
|
-
}
|
|
723
|
-
catch { /* skip */ }
|
|
724
|
-
}
|
|
725
|
-
const reviewsDir = join(boardDir, 'reviews');
|
|
726
|
-
const reviewResults = [];
|
|
727
|
-
if (existsSync(reviewsDir)) {
|
|
728
|
-
try {
|
|
729
|
-
for (const f of readdirSync(reviewsDir).filter(f => f.endsWith('.json'))) {
|
|
730
|
-
const content = readFileIfExists(join(reviewsDir, f));
|
|
731
|
-
if (content) {
|
|
732
|
-
reviewResults.push(JSON.parse(content));
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
catch { /* skip */ }
|
|
737
|
-
}
|
|
738
|
-
return { boardId, progressLog, outputFiles, reviewResults };
|
|
235
|
+
const outputFiles = listDirFiles(join(boardDir, 'out'), '.md');
|
|
236
|
+
const reviewResults = readReviewResults(join(boardDir, 'reviews'));
|
|
237
|
+
const executionLogs = listDirFiles(join(boardDir, 'logs'), '.log').sort();
|
|
238
|
+
return { boardId, progressLog, outputFiles, reviewResults, executionLogs };
|
|
739
239
|
}
|
|
740
240
|
/** @deprecated Use getNextBoardId — kept for migration compatibility */
|
|
741
241
|
export function getNextSprintId(sprints) {
|
|
@@ -759,27 +259,8 @@ export function parseSprintArtifacts(workingDir, sprintId) {
|
|
|
759
259
|
if (!existsSync(sandboxDir))
|
|
760
260
|
return null;
|
|
761
261
|
const progressLog = readFileIfExists(join(sandboxDir, 'progress.md')) ?? '';
|
|
762
|
-
const
|
|
763
|
-
|
|
764
|
-
if (existsSync(outDir)) {
|
|
765
|
-
try {
|
|
766
|
-
outputFiles = readdirSync(outDir).filter(f => f.endsWith('.md'));
|
|
767
|
-
}
|
|
768
|
-
catch { /* skip */ }
|
|
769
|
-
}
|
|
770
|
-
const reviewsDir = join(sandboxDir, 'reviews');
|
|
771
|
-
const reviewResults = [];
|
|
772
|
-
if (existsSync(reviewsDir)) {
|
|
773
|
-
try {
|
|
774
|
-
for (const f of readdirSync(reviewsDir).filter(f => f.endsWith('.json'))) {
|
|
775
|
-
const content = readFileIfExists(join(reviewsDir, f));
|
|
776
|
-
if (content) {
|
|
777
|
-
reviewResults.push(JSON.parse(content));
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
catch { /* skip */ }
|
|
782
|
-
}
|
|
262
|
+
const outputFiles = listDirFiles(join(sandboxDir, 'out'), '.md');
|
|
263
|
+
const reviewResults = readReviewResults(join(sandboxDir, 'reviews'));
|
|
783
264
|
return { sprintId, progressLog, outputFiles, reviewResults };
|
|
784
265
|
}
|
|
785
266
|
//# sourceMappingURL=parser.js.map
|