@sienklogic/plan-build-run 2.0.0
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/CHANGELOG.md +56 -0
- package/CLAUDE.md +149 -0
- package/LICENSE +21 -0
- package/README.md +247 -0
- package/dashboard/bin/cli.js +25 -0
- package/dashboard/package.json +34 -0
- package/dashboard/public/.gitkeep +0 -0
- package/dashboard/public/css/layout.css +406 -0
- package/dashboard/public/css/status-colors.css +98 -0
- package/dashboard/public/js/htmx-title.js +5 -0
- package/dashboard/public/js/sidebar-toggle.js +20 -0
- package/dashboard/src/app.js +78 -0
- package/dashboard/src/middleware/errorHandler.js +52 -0
- package/dashboard/src/middleware/notFoundHandler.js +9 -0
- package/dashboard/src/repositories/planning.repository.js +128 -0
- package/dashboard/src/routes/events.routes.js +40 -0
- package/dashboard/src/routes/index.routes.js +31 -0
- package/dashboard/src/routes/pages.routes.js +195 -0
- package/dashboard/src/server.js +42 -0
- package/dashboard/src/services/dashboard.service.js +222 -0
- package/dashboard/src/services/phase.service.js +167 -0
- package/dashboard/src/services/project.service.js +57 -0
- package/dashboard/src/services/roadmap.service.js +171 -0
- package/dashboard/src/services/sse.service.js +58 -0
- package/dashboard/src/services/todo.service.js +254 -0
- package/dashboard/src/services/watcher.service.js +48 -0
- package/dashboard/src/views/coming-soon.ejs +11 -0
- package/dashboard/src/views/error.ejs +13 -0
- package/dashboard/src/views/index.ejs +5 -0
- package/dashboard/src/views/layout.ejs +1 -0
- package/dashboard/src/views/partials/dashboard-content.ejs +77 -0
- package/dashboard/src/views/partials/footer.ejs +3 -0
- package/dashboard/src/views/partials/head.ejs +21 -0
- package/dashboard/src/views/partials/header.ejs +12 -0
- package/dashboard/src/views/partials/layout-bottom.ejs +15 -0
- package/dashboard/src/views/partials/layout-top.ejs +8 -0
- package/dashboard/src/views/partials/phase-content.ejs +181 -0
- package/dashboard/src/views/partials/phases-content.ejs +117 -0
- package/dashboard/src/views/partials/roadmap-content.ejs +142 -0
- package/dashboard/src/views/partials/sidebar.ejs +38 -0
- package/dashboard/src/views/partials/todo-create-content.ejs +53 -0
- package/dashboard/src/views/partials/todo-detail-content.ejs +38 -0
- package/dashboard/src/views/partials/todos-content.ejs +53 -0
- package/dashboard/src/views/phase-detail.ejs +5 -0
- package/dashboard/src/views/phases.ejs +5 -0
- package/dashboard/src/views/roadmap.ejs +5 -0
- package/dashboard/src/views/todo-create.ejs +5 -0
- package/dashboard/src/views/todo-detail.ejs +5 -0
- package/dashboard/src/views/todos.ejs +5 -0
- package/package.json +57 -0
- package/plugins/pbr/.claude-plugin/plugin.json +13 -0
- package/plugins/pbr/UI-CONSISTENCY-GAPS.md +61 -0
- package/plugins/pbr/agents/codebase-mapper.md +271 -0
- package/plugins/pbr/agents/debugger.md +281 -0
- package/plugins/pbr/agents/executor.md +407 -0
- package/plugins/pbr/agents/general.md +164 -0
- package/plugins/pbr/agents/integration-checker.md +141 -0
- package/plugins/pbr/agents/plan-checker.md +280 -0
- package/plugins/pbr/agents/planner.md +358 -0
- package/plugins/pbr/agents/researcher.md +363 -0
- package/plugins/pbr/agents/synthesizer.md +230 -0
- package/plugins/pbr/agents/verifier.md +454 -0
- package/plugins/pbr/commands/begin.md +5 -0
- package/plugins/pbr/commands/build.md +5 -0
- package/plugins/pbr/commands/config.md +5 -0
- package/plugins/pbr/commands/continue.md +5 -0
- package/plugins/pbr/commands/debug.md +5 -0
- package/plugins/pbr/commands/discuss.md +5 -0
- package/plugins/pbr/commands/explore.md +5 -0
- package/plugins/pbr/commands/health.md +5 -0
- package/plugins/pbr/commands/help.md +5 -0
- package/plugins/pbr/commands/import.md +5 -0
- package/plugins/pbr/commands/milestone.md +5 -0
- package/plugins/pbr/commands/note.md +5 -0
- package/plugins/pbr/commands/pause.md +5 -0
- package/plugins/pbr/commands/plan.md +5 -0
- package/plugins/pbr/commands/quick.md +5 -0
- package/plugins/pbr/commands/resume.md +5 -0
- package/plugins/pbr/commands/review.md +5 -0
- package/plugins/pbr/commands/scan.md +5 -0
- package/plugins/pbr/commands/setup.md +5 -0
- package/plugins/pbr/commands/status.md +5 -0
- package/plugins/pbr/commands/todo.md +5 -0
- package/plugins/pbr/contexts/dev.md +27 -0
- package/plugins/pbr/contexts/research.md +28 -0
- package/plugins/pbr/contexts/review.md +36 -0
- package/plugins/pbr/hooks/hooks.json +183 -0
- package/plugins/pbr/references/agent-anti-patterns.md +24 -0
- package/plugins/pbr/references/agent-interactions.md +134 -0
- package/plugins/pbr/references/agent-teams.md +54 -0
- package/plugins/pbr/references/checkpoints.md +157 -0
- package/plugins/pbr/references/common-bug-patterns.md +13 -0
- package/plugins/pbr/references/continuation-format.md +212 -0
- package/plugins/pbr/references/deviation-rules.md +112 -0
- package/plugins/pbr/references/git-integration.md +226 -0
- package/plugins/pbr/references/integration-patterns.md +117 -0
- package/plugins/pbr/references/model-profiles.md +99 -0
- package/plugins/pbr/references/model-selection.md +31 -0
- package/plugins/pbr/references/pbr-rules.md +193 -0
- package/plugins/pbr/references/plan-authoring.md +181 -0
- package/plugins/pbr/references/plan-format.md +283 -0
- package/plugins/pbr/references/planning-config.md +213 -0
- package/plugins/pbr/references/questioning.md +214 -0
- package/plugins/pbr/references/reading-verification.md +127 -0
- package/plugins/pbr/references/stub-patterns.md +160 -0
- package/plugins/pbr/references/subagent-coordination.md +119 -0
- package/plugins/pbr/references/ui-formatting.md +399 -0
- package/plugins/pbr/references/verification-patterns.md +198 -0
- package/plugins/pbr/references/wave-execution.md +95 -0
- package/plugins/pbr/scripts/auto-continue.js +80 -0
- package/plugins/pbr/scripts/check-dangerous-commands.js +136 -0
- package/plugins/pbr/scripts/check-doc-sprawl.js +102 -0
- package/plugins/pbr/scripts/check-phase-boundary.js +196 -0
- package/plugins/pbr/scripts/check-plan-format.js +270 -0
- package/plugins/pbr/scripts/check-roadmap-sync.js +252 -0
- package/plugins/pbr/scripts/check-skill-workflow.js +262 -0
- package/plugins/pbr/scripts/check-state-sync.js +476 -0
- package/plugins/pbr/scripts/check-subagent-output.js +144 -0
- package/plugins/pbr/scripts/config-schema.json +251 -0
- package/plugins/pbr/scripts/context-budget-check.js +287 -0
- package/plugins/pbr/scripts/event-handler.js +151 -0
- package/plugins/pbr/scripts/event-logger.js +92 -0
- package/plugins/pbr/scripts/hook-logger.js +76 -0
- package/plugins/pbr/scripts/hooks-schema.json +79 -0
- package/plugins/pbr/scripts/log-subagent.js +152 -0
- package/plugins/pbr/scripts/log-tool-failure.js +88 -0
- package/plugins/pbr/scripts/pbr-tools.js +1301 -0
- package/plugins/pbr/scripts/post-write-dispatch.js +66 -0
- package/plugins/pbr/scripts/post-write-quality.js +207 -0
- package/plugins/pbr/scripts/pre-bash-dispatch.js +56 -0
- package/plugins/pbr/scripts/pre-write-dispatch.js +62 -0
- package/plugins/pbr/scripts/progress-tracker.js +228 -0
- package/plugins/pbr/scripts/session-cleanup.js +254 -0
- package/plugins/pbr/scripts/status-line.js +285 -0
- package/plugins/pbr/scripts/suggest-compact.js +119 -0
- package/plugins/pbr/scripts/task-completed.js +45 -0
- package/plugins/pbr/scripts/track-context-budget.js +119 -0
- package/plugins/pbr/scripts/validate-commit.js +200 -0
- package/plugins/pbr/scripts/validate-plugin-structure.js +172 -0
- package/plugins/pbr/skills/begin/SKILL.md +545 -0
- package/plugins/pbr/skills/begin/templates/PROJECT.md.tmpl +33 -0
- package/plugins/pbr/skills/begin/templates/REQUIREMENTS.md.tmpl +18 -0
- package/plugins/pbr/skills/begin/templates/STATE.md.tmpl +49 -0
- package/plugins/pbr/skills/begin/templates/config.json.tmpl +63 -0
- package/plugins/pbr/skills/begin/templates/researcher-prompt.md.tmpl +19 -0
- package/plugins/pbr/skills/begin/templates/roadmap-prompt.md.tmpl +30 -0
- package/plugins/pbr/skills/begin/templates/synthesis-prompt.md.tmpl +16 -0
- package/plugins/pbr/skills/build/SKILL.md +962 -0
- package/plugins/pbr/skills/config/SKILL.md +241 -0
- package/plugins/pbr/skills/continue/SKILL.md +127 -0
- package/plugins/pbr/skills/debug/SKILL.md +489 -0
- package/plugins/pbr/skills/debug/templates/continuation-prompt.md.tmpl +16 -0
- package/plugins/pbr/skills/debug/templates/initial-investigation-prompt.md.tmpl +27 -0
- package/plugins/pbr/skills/discuss/SKILL.md +338 -0
- package/plugins/pbr/skills/discuss/templates/CONTEXT.md.tmpl +61 -0
- package/plugins/pbr/skills/discuss/templates/decision-categories.md +9 -0
- package/plugins/pbr/skills/explore/SKILL.md +362 -0
- package/plugins/pbr/skills/health/SKILL.md +186 -0
- package/plugins/pbr/skills/health/templates/check-pattern.md.tmpl +30 -0
- package/plugins/pbr/skills/health/templates/output-format.md.tmpl +63 -0
- package/plugins/pbr/skills/help/SKILL.md +140 -0
- package/plugins/pbr/skills/import/SKILL.md +490 -0
- package/plugins/pbr/skills/milestone/SKILL.md +673 -0
- package/plugins/pbr/skills/milestone/templates/audit-report.md.tmpl +48 -0
- package/plugins/pbr/skills/milestone/templates/stats-file.md.tmpl +30 -0
- package/plugins/pbr/skills/note/SKILL.md +212 -0
- package/plugins/pbr/skills/pause/SKILL.md +235 -0
- package/plugins/pbr/skills/pause/templates/continue-here.md.tmpl +71 -0
- package/plugins/pbr/skills/plan/SKILL.md +628 -0
- package/plugins/pbr/skills/plan/decimal-phase-calc.md +98 -0
- package/plugins/pbr/skills/plan/templates/checker-prompt.md.tmpl +21 -0
- package/plugins/pbr/skills/plan/templates/gap-closure-prompt.md.tmpl +32 -0
- package/plugins/pbr/skills/plan/templates/planner-prompt.md.tmpl +38 -0
- package/plugins/pbr/skills/plan/templates/researcher-prompt.md.tmpl +19 -0
- package/plugins/pbr/skills/plan/templates/revision-prompt.md.tmpl +23 -0
- package/plugins/pbr/skills/quick/SKILL.md +335 -0
- package/plugins/pbr/skills/resume/SKILL.md +388 -0
- package/plugins/pbr/skills/review/SKILL.md +652 -0
- package/plugins/pbr/skills/review/templates/debugger-prompt.md.tmpl +60 -0
- package/plugins/pbr/skills/review/templates/gap-planner-prompt.md.tmpl +40 -0
- package/plugins/pbr/skills/review/templates/verifier-prompt.md.tmpl +115 -0
- package/plugins/pbr/skills/scan/SKILL.md +269 -0
- package/plugins/pbr/skills/scan/templates/mapper-prompt.md.tmpl +201 -0
- package/plugins/pbr/skills/setup/SKILL.md +227 -0
- package/plugins/pbr/skills/shared/commit-planning-docs.md +35 -0
- package/plugins/pbr/skills/shared/config-loading.md +102 -0
- package/plugins/pbr/skills/shared/context-budget.md +40 -0
- package/plugins/pbr/skills/shared/context-loader-task.md +86 -0
- package/plugins/pbr/skills/shared/digest-select.md +79 -0
- package/plugins/pbr/skills/shared/domain-probes.md +125 -0
- package/plugins/pbr/skills/shared/error-reporting.md +79 -0
- package/plugins/pbr/skills/shared/gate-prompts.md +388 -0
- package/plugins/pbr/skills/shared/phase-argument-parsing.md +45 -0
- package/plugins/pbr/skills/shared/progress-display.md +53 -0
- package/plugins/pbr/skills/shared/revision-loop.md +81 -0
- package/plugins/pbr/skills/shared/state-loading.md +62 -0
- package/plugins/pbr/skills/shared/state-update.md +161 -0
- package/plugins/pbr/skills/shared/universal-anti-patterns.md +33 -0
- package/plugins/pbr/skills/status/SKILL.md +353 -0
- package/plugins/pbr/skills/todo/SKILL.md +181 -0
- package/plugins/pbr/templates/CONTEXT.md.tmpl +52 -0
- package/plugins/pbr/templates/INTEGRATION-REPORT.md.tmpl +151 -0
- package/plugins/pbr/templates/RESEARCH-SUMMARY.md.tmpl +97 -0
- package/plugins/pbr/templates/ROADMAP.md.tmpl +40 -0
- package/plugins/pbr/templates/SUMMARY.md.tmpl +81 -0
- package/plugins/pbr/templates/VERIFICATION-DETAIL.md.tmpl +116 -0
- package/plugins/pbr/templates/codebase/ARCHITECTURE.md.tmpl +98 -0
- package/plugins/pbr/templates/codebase/CONCERNS.md.tmpl +93 -0
- package/plugins/pbr/templates/codebase/CONVENTIONS.md.tmpl +104 -0
- package/plugins/pbr/templates/codebase/INTEGRATIONS.md.tmpl +78 -0
- package/plugins/pbr/templates/codebase/STACK.md.tmpl +78 -0
- package/plugins/pbr/templates/codebase/STRUCTURE.md.tmpl +80 -0
- package/plugins/pbr/templates/codebase/TESTING.md.tmpl +107 -0
- package/plugins/pbr/templates/continue-here.md.tmpl +73 -0
- package/plugins/pbr/templates/prompt-partials/phase-project-context.md.tmpl +37 -0
- package/plugins/pbr/templates/research/ARCHITECTURE.md.tmpl +124 -0
- package/plugins/pbr/templates/research/STACK.md.tmpl +71 -0
- package/plugins/pbr/templates/research/SUMMARY.md.tmpl +112 -0
- package/plugins/pbr/templates/research-outputs/phase-research.md.tmpl +81 -0
- package/plugins/pbr/templates/research-outputs/project-research.md.tmpl +99 -0
- package/plugins/pbr/templates/research-outputs/synthesis.md.tmpl +36 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PostToolUse dispatcher for Write|Edit hooks.
|
|
5
|
+
*
|
|
6
|
+
* Consolidates check-plan-format.js and check-roadmap-sync.js
|
|
7
|
+
* into a single process, reading stdin once and routing to the
|
|
8
|
+
* appropriate check based on the file path. This halves the
|
|
9
|
+
* process spawns per Write/Edit call.
|
|
10
|
+
*
|
|
11
|
+
* Routing:
|
|
12
|
+
* - PLAN.md or SUMMARY*.md → plan format validation
|
|
13
|
+
* - STATE.md → roadmap sync check
|
|
14
|
+
* - SUMMARY*.md or VERIFICATION.md in .planning/phases/ → state sync (auto-update tracking files)
|
|
15
|
+
* - Other files → exit immediately (no work needed)
|
|
16
|
+
*
|
|
17
|
+
* Exit codes:
|
|
18
|
+
* 0 = always (PostToolUse hooks are advisory)
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const { checkPlanWrite } = require('./check-plan-format');
|
|
22
|
+
const { checkSync } = require('./check-roadmap-sync');
|
|
23
|
+
const { checkStateSync } = require('./check-state-sync');
|
|
24
|
+
|
|
25
|
+
function main() {
|
|
26
|
+
let input = '';
|
|
27
|
+
|
|
28
|
+
process.stdin.setEncoding('utf8');
|
|
29
|
+
process.stdin.on('data', (chunk) => { input += chunk; });
|
|
30
|
+
process.stdin.on('end', () => {
|
|
31
|
+
try {
|
|
32
|
+
const data = JSON.parse(input);
|
|
33
|
+
|
|
34
|
+
// Plan format check (PLAN.md, SUMMARY*.md)
|
|
35
|
+
// Note: SUMMARY files intentionally trigger BOTH this check AND the state-sync
|
|
36
|
+
// check below. The plan format check validates frontmatter structure, while
|
|
37
|
+
// state-sync auto-updates ROADMAP.md and STATE.md tracking fields.
|
|
38
|
+
const planResult = checkPlanWrite(data);
|
|
39
|
+
if (planResult) {
|
|
40
|
+
process.stdout.write(JSON.stringify(planResult.output));
|
|
41
|
+
process.exit(0);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Roadmap sync check (STATE.md)
|
|
45
|
+
const syncResult = checkSync(data);
|
|
46
|
+
if (syncResult) {
|
|
47
|
+
process.stdout.write(JSON.stringify(syncResult.output));
|
|
48
|
+
process.exit(0);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// State sync check (SUMMARY/VERIFICATION → STATE.md + ROADMAP.md)
|
|
52
|
+
const stateSyncResult = checkStateSync(data);
|
|
53
|
+
if (stateSyncResult) {
|
|
54
|
+
process.stdout.write(JSON.stringify(stateSyncResult.output));
|
|
55
|
+
process.exit(0);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
process.exit(0);
|
|
59
|
+
} catch (_e) {
|
|
60
|
+
// Don't block on parse errors
|
|
61
|
+
process.exit(0);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (require.main === module) { main(); }
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PostToolUse quality check hook for Write|Edit.
|
|
5
|
+
*
|
|
6
|
+
* Runs opt-in code quality checks on modified JS/TS files:
|
|
7
|
+
* 1. autoFormat: Run Prettier on the file (requires local installation)
|
|
8
|
+
* 2. typeCheck: Run tsc --noEmit filtered to the edited file (requires local typescript)
|
|
9
|
+
* 3. detectConsoleLogs: Warn about console.log statements left in the file
|
|
10
|
+
*
|
|
11
|
+
* All checks disabled by default. Enable via .planning/config.json:
|
|
12
|
+
* { "hooks": { "autoFormat": true, "typeCheck": true, "detectConsoleLogs": true } }
|
|
13
|
+
*
|
|
14
|
+
* Only processes JS/TS files (.js, .jsx, .ts, .tsx, .mjs, .cjs).
|
|
15
|
+
* Silently skips if tools (prettier, tsc) are not installed in the project.
|
|
16
|
+
*
|
|
17
|
+
* Exit codes:
|
|
18
|
+
* 0 = always (PostToolUse hook, advisory only)
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const fs = require('fs');
|
|
22
|
+
const path = require('path');
|
|
23
|
+
const { execSync } = require('child_process');
|
|
24
|
+
const { logHook } = require('./hook-logger');
|
|
25
|
+
|
|
26
|
+
const JS_TS_EXTENSIONS = new Set(['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs']);
|
|
27
|
+
const TS_EXTENSIONS = new Set(['.ts', '.tsx']);
|
|
28
|
+
|
|
29
|
+
function main() {
|
|
30
|
+
let input = '';
|
|
31
|
+
|
|
32
|
+
process.stdin.setEncoding('utf8');
|
|
33
|
+
process.stdin.on('data', (chunk) => { input += chunk; });
|
|
34
|
+
process.stdin.on('end', () => {
|
|
35
|
+
try {
|
|
36
|
+
const data = JSON.parse(input);
|
|
37
|
+
const result = checkQuality(data);
|
|
38
|
+
if (result) {
|
|
39
|
+
process.stdout.write(JSON.stringify(result.output));
|
|
40
|
+
}
|
|
41
|
+
process.exit(0);
|
|
42
|
+
} catch (_e) {
|
|
43
|
+
process.exit(0);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Core quality check logic for use by dispatchers or standalone.
|
|
50
|
+
* @param {Object} data - Parsed hook input (tool_input, etc.)
|
|
51
|
+
* @returns {null|{output: Object}} null if no checks fired, result otherwise
|
|
52
|
+
*/
|
|
53
|
+
function checkQuality(data) {
|
|
54
|
+
const filePath = data.tool_input?.file_path || data.tool_input?.path || '';
|
|
55
|
+
if (!filePath) return null;
|
|
56
|
+
|
|
57
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
58
|
+
if (!JS_TS_EXTENSIONS.has(ext)) return null;
|
|
59
|
+
|
|
60
|
+
const cwd = process.cwd();
|
|
61
|
+
const config = loadHooksConfig(cwd);
|
|
62
|
+
|
|
63
|
+
// No quality hooks enabled — early exit
|
|
64
|
+
if (!config.autoFormat && !config.typeCheck && !config.detectConsoleLogs) return null;
|
|
65
|
+
|
|
66
|
+
const messages = [];
|
|
67
|
+
|
|
68
|
+
// 1. Auto-format with Prettier
|
|
69
|
+
if (config.autoFormat) {
|
|
70
|
+
const msg = runPrettier(filePath, cwd);
|
|
71
|
+
if (msg) messages.push(msg);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// 2. TypeScript type-check (only for .ts/.tsx)
|
|
75
|
+
if (config.typeCheck && TS_EXTENSIONS.has(ext)) {
|
|
76
|
+
const msg = runTypeCheck(filePath, cwd);
|
|
77
|
+
if (msg) messages.push(msg);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// 3. Console.log detection
|
|
81
|
+
if (config.detectConsoleLogs) {
|
|
82
|
+
const msg = detectConsoleLogs(filePath);
|
|
83
|
+
if (msg) messages.push(msg);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (messages.length === 0) return null;
|
|
87
|
+
|
|
88
|
+
logHook('post-write-quality', 'PostToolUse', 'quality-check', {
|
|
89
|
+
file: path.basename(filePath),
|
|
90
|
+
checks: messages.length
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
output: {
|
|
95
|
+
additionalContext: messages.join('\n')
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Load the hooks section from .planning/config.json.
|
|
102
|
+
* Returns {} if not found or not configured.
|
|
103
|
+
*/
|
|
104
|
+
function loadHooksConfig(cwd) {
|
|
105
|
+
const configPath = path.join(cwd, '.planning', 'config.json');
|
|
106
|
+
if (!fs.existsSync(configPath)) return {};
|
|
107
|
+
try {
|
|
108
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
109
|
+
return config.hooks || {};
|
|
110
|
+
} catch (_e) {
|
|
111
|
+
return {};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Find a locally installed binary in node_modules/.bin/.
|
|
117
|
+
* Returns the full path or null if not found.
|
|
118
|
+
*/
|
|
119
|
+
function findLocalBin(cwd, name) {
|
|
120
|
+
const candidates = process.platform === 'win32'
|
|
121
|
+
? [path.join(cwd, 'node_modules', '.bin', name + '.cmd'),
|
|
122
|
+
path.join(cwd, 'node_modules', '.bin', name)]
|
|
123
|
+
: [path.join(cwd, 'node_modules', '.bin', name)];
|
|
124
|
+
return candidates.find(c => fs.existsSync(c)) || null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Run Prettier on the file. Returns a message string or null.
|
|
129
|
+
*/
|
|
130
|
+
function runPrettier(filePath, cwd) {
|
|
131
|
+
const bin = findLocalBin(cwd, 'prettier');
|
|
132
|
+
if (!bin) return null;
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
execSync(`"${bin}" --write "${filePath}"`, {
|
|
136
|
+
cwd,
|
|
137
|
+
timeout: 15000,
|
|
138
|
+
stdio: 'pipe',
|
|
139
|
+
});
|
|
140
|
+
return `[Auto-format] Prettier reformatted ${path.basename(filePath)}. File on disk may differ from context — re-read before further edits.`;
|
|
141
|
+
} catch (_e) {
|
|
142
|
+
// Prettier failed (syntax error, unsupported file, etc.) — skip
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Run tsc --noEmit and filter to errors in the modified file.
|
|
149
|
+
* Returns a message string or null.
|
|
150
|
+
*/
|
|
151
|
+
function runTypeCheck(filePath, cwd) {
|
|
152
|
+
const bin = findLocalBin(cwd, 'tsc');
|
|
153
|
+
if (!bin) return null;
|
|
154
|
+
if (!fs.existsSync(path.join(cwd, 'tsconfig.json'))) return null;
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
execSync(`"${bin}" --noEmit`, {
|
|
158
|
+
cwd,
|
|
159
|
+
timeout: 30000,
|
|
160
|
+
stdio: 'pipe',
|
|
161
|
+
encoding: 'utf8',
|
|
162
|
+
});
|
|
163
|
+
return null; // Clean pass — no errors
|
|
164
|
+
} catch (e) {
|
|
165
|
+
const output = (e.stdout || '').toString();
|
|
166
|
+
const basename = path.basename(filePath);
|
|
167
|
+
const relevantLines = output.split('\n')
|
|
168
|
+
.filter(line => line.includes(basename) && /error TS\d+/.test(line));
|
|
169
|
+
|
|
170
|
+
if (relevantLines.length === 0) return null;
|
|
171
|
+
const detail = relevantLines.slice(0, 3).join('\n ');
|
|
172
|
+
const extra = relevantLines.length > 3 ? `\n ...and ${relevantLines.length - 3} more` : '';
|
|
173
|
+
return `[Type Check] ${relevantLines.length} error(s) in ${basename}:\n ${detail}${extra}`;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Scan file for console.log statements. Returns a message string or null.
|
|
179
|
+
*/
|
|
180
|
+
function detectConsoleLogs(filePath) {
|
|
181
|
+
if (!fs.existsSync(filePath)) return null;
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
185
|
+
const lines = content.split('\n');
|
|
186
|
+
const matches = [];
|
|
187
|
+
|
|
188
|
+
for (let i = 0; i < lines.length; i++) {
|
|
189
|
+
const line = lines[i];
|
|
190
|
+
if (/^\s*\/\//.test(line)) continue; // skip single-line comments
|
|
191
|
+
if (/\bconsole\.log\s*\(/.test(line)) {
|
|
192
|
+
matches.push({ line: i + 1, text: line.trim().substring(0, 80) });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (matches.length === 0) return null;
|
|
197
|
+
|
|
198
|
+
const detail = matches.slice(0, 3).map(m => ` L${m.line}: ${m.text}`).join('\n');
|
|
199
|
+
const extra = matches.length > 3 ? `\n ...and ${matches.length - 3} more` : '';
|
|
200
|
+
return `[Console.log] ${matches.length} console.log(s) in ${path.basename(filePath)}:\n${detail}${extra}`;
|
|
201
|
+
} catch (_e) {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
module.exports = { checkQuality, loadHooksConfig, findLocalBin, runPrettier, runTypeCheck, detectConsoleLogs };
|
|
207
|
+
if (require.main === module) { main(); }
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PreToolUse dispatcher for Bash hooks.
|
|
5
|
+
*
|
|
6
|
+
* Consolidates check-dangerous-commands.js and validate-commit.js
|
|
7
|
+
* into a single process, reading stdin once and routing to both
|
|
8
|
+
* checks sequentially. This halves the process spawns per Bash call.
|
|
9
|
+
*
|
|
10
|
+
* Check order:
|
|
11
|
+
* 1. Dangerous commands check (can block destructive operations)
|
|
12
|
+
* 2. Commit validation (can block badly-formatted commits)
|
|
13
|
+
*
|
|
14
|
+
* Exit codes:
|
|
15
|
+
* 0 = allowed or warning only
|
|
16
|
+
* 2 = blocked (dangerous command or invalid commit format)
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const { logHook } = require('./hook-logger');
|
|
20
|
+
const { checkDangerous } = require('./check-dangerous-commands');
|
|
21
|
+
const { checkCommit } = require('./validate-commit');
|
|
22
|
+
|
|
23
|
+
function main() {
|
|
24
|
+
let input = '';
|
|
25
|
+
|
|
26
|
+
process.stdin.setEncoding('utf8');
|
|
27
|
+
process.stdin.on('data', (chunk) => { input += chunk; });
|
|
28
|
+
process.stdin.on('end', () => {
|
|
29
|
+
try {
|
|
30
|
+
const data = JSON.parse(input);
|
|
31
|
+
|
|
32
|
+
// Dangerous commands check first — can block
|
|
33
|
+
const dangerousResult = checkDangerous(data);
|
|
34
|
+
if (dangerousResult) {
|
|
35
|
+
logHook('pre-bash-dispatch', 'PreToolUse', 'dispatched', { handler: 'check-dangerous-commands' });
|
|
36
|
+
process.stdout.write(JSON.stringify(dangerousResult.output));
|
|
37
|
+
process.exit(dangerousResult.exitCode);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Commit validation check — can block
|
|
41
|
+
const commitResult = checkCommit(data);
|
|
42
|
+
if (commitResult) {
|
|
43
|
+
logHook('pre-bash-dispatch', 'PreToolUse', 'dispatched', { handler: 'validate-commit' });
|
|
44
|
+
process.stdout.write(JSON.stringify(commitResult.output));
|
|
45
|
+
process.exit(commitResult.exitCode);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
process.exit(0);
|
|
49
|
+
} catch (_e) {
|
|
50
|
+
// Don't block on errors
|
|
51
|
+
process.exit(0);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (require.main === module) { main(); }
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PreToolUse dispatcher for Write|Edit hooks.
|
|
5
|
+
*
|
|
6
|
+
* Consolidates check-skill-workflow.js, check-phase-boundary.js,
|
|
7
|
+
* and check-doc-sprawl.js into a single process, reading stdin once
|
|
8
|
+
* and running all checks sequentially.
|
|
9
|
+
*
|
|
10
|
+
* Check order matters: skill workflow runs first (can block writes
|
|
11
|
+
* that violate planning rules), then phase boundary (can block or
|
|
12
|
+
* warn about cross-phase writes), then doc sprawl (blocks new .md/.txt
|
|
13
|
+
* files outside the allowlist when enabled).
|
|
14
|
+
*
|
|
15
|
+
* Exit codes:
|
|
16
|
+
* 0 = allowed or warning only
|
|
17
|
+
* 2 = blocked (workflow violation or phase boundary enforcement)
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const { checkWorkflow } = require('./check-skill-workflow');
|
|
21
|
+
const { checkBoundary } = require('./check-phase-boundary');
|
|
22
|
+
const { checkDocSprawl } = require('./check-doc-sprawl');
|
|
23
|
+
|
|
24
|
+
function main() {
|
|
25
|
+
let input = '';
|
|
26
|
+
|
|
27
|
+
process.stdin.setEncoding('utf8');
|
|
28
|
+
process.stdin.on('data', (chunk) => { input += chunk; });
|
|
29
|
+
process.stdin.on('end', () => {
|
|
30
|
+
try {
|
|
31
|
+
const data = JSON.parse(input);
|
|
32
|
+
|
|
33
|
+
// Skill workflow check first — can block
|
|
34
|
+
const workflowResult = checkWorkflow(data);
|
|
35
|
+
if (workflowResult) {
|
|
36
|
+
process.stdout.write(JSON.stringify(workflowResult.output));
|
|
37
|
+
process.exit(workflowResult.exitCode || 0);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Phase boundary check — can block or warn
|
|
41
|
+
const boundaryResult = checkBoundary(data);
|
|
42
|
+
if (boundaryResult) {
|
|
43
|
+
process.stdout.write(JSON.stringify(boundaryResult.output));
|
|
44
|
+
process.exit(boundaryResult.exitCode || 0);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Doc sprawl check — blocks new .md/.txt outside allowlist
|
|
48
|
+
const sprawlResult = checkDocSprawl(data);
|
|
49
|
+
if (sprawlResult) {
|
|
50
|
+
process.stdout.write(JSON.stringify(sprawlResult.output));
|
|
51
|
+
process.exit(sprawlResult.exitCode || 0);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
process.exit(0);
|
|
55
|
+
} catch (_e) {
|
|
56
|
+
// Don't block on errors
|
|
57
|
+
process.exit(0);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (require.main === module) { main(); }
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SessionStart hook: Auto-detects .planning/ directory and injects
|
|
5
|
+
* project state as additionalContext.
|
|
6
|
+
*
|
|
7
|
+
* If no .planning/ directory exists, exits silently (non-Plan-Build-Run project).
|
|
8
|
+
* If STATE.md exists, reads and outputs a concise summary.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const os = require('os');
|
|
14
|
+
const { execSync } = require('child_process');
|
|
15
|
+
const { logHook } = require('./hook-logger');
|
|
16
|
+
const { logEvent } = require('./event-logger');
|
|
17
|
+
const { configLoad } = require('./pbr-tools');
|
|
18
|
+
|
|
19
|
+
function main() {
|
|
20
|
+
const cwd = process.cwd();
|
|
21
|
+
const planningDir = path.join(cwd, '.planning');
|
|
22
|
+
const stateFile = path.join(planningDir, 'STATE.md');
|
|
23
|
+
|
|
24
|
+
// Not a Plan-Build-Run project
|
|
25
|
+
if (!fs.existsSync(planningDir)) {
|
|
26
|
+
process.exit(0);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Reset compaction counter for new session
|
|
30
|
+
const { resetCounter } = require('./suggest-compact');
|
|
31
|
+
resetCounter(planningDir);
|
|
32
|
+
|
|
33
|
+
const context = buildContext(planningDir, stateFile);
|
|
34
|
+
|
|
35
|
+
if (context) {
|
|
36
|
+
const output = {
|
|
37
|
+
additionalContext: context
|
|
38
|
+
};
|
|
39
|
+
process.stdout.write(JSON.stringify(output));
|
|
40
|
+
logHook('progress-tracker', 'SessionStart', 'injected', { hasState: true });
|
|
41
|
+
logEvent('workflow', 'session-start', { hasState: true });
|
|
42
|
+
} else {
|
|
43
|
+
logHook('progress-tracker', 'SessionStart', 'skipped', { hasState: false });
|
|
44
|
+
logEvent('workflow', 'session-start', { hasState: false });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
process.exit(0);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function buildContext(planningDir, stateFile) {
|
|
51
|
+
const parts = [];
|
|
52
|
+
|
|
53
|
+
parts.push('[Plan-Build-Run Project Detected]');
|
|
54
|
+
|
|
55
|
+
// Git context
|
|
56
|
+
try {
|
|
57
|
+
const branch = execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf8', timeout: 3000 }).trim();
|
|
58
|
+
const porcelain = execSync('git status --porcelain', { encoding: 'utf8', timeout: 3000 }).trim();
|
|
59
|
+
const uncommitted = porcelain ? porcelain.split('\n').length : 0;
|
|
60
|
+
const recentCommits = execSync('git log -5 --oneline', { encoding: 'utf8', timeout: 3000 }).trim();
|
|
61
|
+
parts.push(`\nGit: ${branch} (${uncommitted} uncommitted file${uncommitted !== 1 ? 's' : ''})`);
|
|
62
|
+
if (recentCommits) {
|
|
63
|
+
parts.push(`Recent commits:\n${recentCommits}`);
|
|
64
|
+
}
|
|
65
|
+
} catch (_e) {
|
|
66
|
+
// Not a git repo or git not available — skip
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Read STATE.md if it exists
|
|
70
|
+
if (fs.existsSync(stateFile)) {
|
|
71
|
+
const state = fs.readFileSync(stateFile, 'utf8');
|
|
72
|
+
|
|
73
|
+
// Extract key sections
|
|
74
|
+
const position = extractSection(state, 'Current Position');
|
|
75
|
+
if (position) {
|
|
76
|
+
parts.push(`\nPosition:\n${position}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const blockers = extractSection(state, 'Blockers/Concerns');
|
|
80
|
+
if (blockers && !blockers.includes('None')) {
|
|
81
|
+
parts.push(`\nBlockers:\n${blockers}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const continuity = extractSection(state, 'Session Continuity');
|
|
85
|
+
if (continuity) {
|
|
86
|
+
parts.push(`\nLast Session:\n${continuity}`);
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
parts.push('\nNo STATE.md found. Run /pbr:begin to initialize or /pbr:status to check.');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Check for .continue-here.md files
|
|
93
|
+
const phasesDir = path.join(planningDir, 'phases');
|
|
94
|
+
if (fs.existsSync(phasesDir)) {
|
|
95
|
+
const continueFiles = findContinueFiles(phasesDir);
|
|
96
|
+
if (continueFiles.length > 0) {
|
|
97
|
+
parts.push(`\nPaused work found: ${continueFiles.join(', ')}`);
|
|
98
|
+
parts.push('Run /pbr:resume to pick up where you left off.');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Check for config and validate
|
|
103
|
+
const config = configLoad(planningDir);
|
|
104
|
+
if (config) {
|
|
105
|
+
parts.push(`\nConfig: depth=${config.depth || 'standard'}, mode=${config.mode || 'interactive'}`);
|
|
106
|
+
|
|
107
|
+
// Validate config against schema (reuse already-loaded config)
|
|
108
|
+
const schemaPath = path.join(__dirname, 'config-schema.json');
|
|
109
|
+
if (fs.existsSync(schemaPath)) {
|
|
110
|
+
const { configValidate } = require('./pbr-tools');
|
|
111
|
+
const validation = configValidate(config);
|
|
112
|
+
if (validation.warnings.length > 0) {
|
|
113
|
+
parts.push(`\nConfig warnings: ${validation.warnings.join('; ')}`);
|
|
114
|
+
}
|
|
115
|
+
if (validation.errors.length > 0) {
|
|
116
|
+
parts.push(`\nConfig errors: ${validation.errors.join('; ')}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Check for quick notes
|
|
122
|
+
const projectNotesFile = path.join(planningDir, 'NOTES.md');
|
|
123
|
+
const globalNotesFile = path.join(os.homedir(), '.claude', 'notes.md');
|
|
124
|
+
const projectNoteCount = countNotes(projectNotesFile);
|
|
125
|
+
const globalNoteCount = countNotes(globalNotesFile);
|
|
126
|
+
if (projectNoteCount > 0 || globalNoteCount > 0) {
|
|
127
|
+
const noteParts = [];
|
|
128
|
+
if (projectNoteCount > 0) noteParts.push(`${projectNoteCount} project`);
|
|
129
|
+
if (globalNoteCount > 0) noteParts.push(`${globalNoteCount} global`);
|
|
130
|
+
parts.push(`\nNotes: ${noteParts.join(', ')}. \`/pbr:note list\` to review.`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Check ROADMAP/STATE sync (S>M-2)
|
|
134
|
+
const roadmapFile = path.join(planningDir, 'ROADMAP.md');
|
|
135
|
+
if (fs.existsSync(stateFile) && fs.existsSync(roadmapFile)) {
|
|
136
|
+
try {
|
|
137
|
+
const roadmap = fs.readFileSync(roadmapFile, 'utf8');
|
|
138
|
+
const state = fs.readFileSync(stateFile, 'utf8');
|
|
139
|
+
|
|
140
|
+
// Extract current phase from STATE.md
|
|
141
|
+
const phaseMatch = state.match(/Phase:\s*(\d+)\s+of\s+\d+/);
|
|
142
|
+
if (phaseMatch) {
|
|
143
|
+
const currentPhase = parseInt(phaseMatch[1], 10);
|
|
144
|
+
// Check if ROADMAP shows this phase as already verified/complete
|
|
145
|
+
const progressTable = roadmap.match(/## Progress[\s\S]*?\|[\s\S]*?(?=\n##|\s*$)/);
|
|
146
|
+
if (progressTable) {
|
|
147
|
+
const rows = progressTable[0].split('\n').filter(r => r.includes('|'));
|
|
148
|
+
for (const row of rows) {
|
|
149
|
+
const cols = row.split('|').map(c => c.trim()).filter(Boolean);
|
|
150
|
+
if (cols.length >= 4) {
|
|
151
|
+
const phaseNum = parseInt(cols[0], 10);
|
|
152
|
+
const status = cols[3] ? cols[3].toLowerCase() : '';
|
|
153
|
+
if (phaseNum === currentPhase && (status === 'verified' || status === 'complete')) {
|
|
154
|
+
parts.push(`\nWarning: STATE.md may be outdated — ROADMAP.md shows phase ${currentPhase} as ${status}.`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
} catch (_e) {
|
|
161
|
+
// Ignore parse errors
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Check for stale .auto-next signal (S>M-9)
|
|
166
|
+
const autoNextFile = path.join(planningDir, '.auto-next');
|
|
167
|
+
if (fs.existsSync(autoNextFile)) {
|
|
168
|
+
try {
|
|
169
|
+
const stats = fs.statSync(autoNextFile);
|
|
170
|
+
const ageMs = Date.now() - stats.mtimeMs;
|
|
171
|
+
const ageMinutes = Math.floor(ageMs / 60000);
|
|
172
|
+
if (ageMinutes > 10) {
|
|
173
|
+
parts.push(`\nWarning: Stale .auto-next signal found (${ageMinutes} minutes old). This may trigger an unexpected command. Consider deleting .planning/.auto-next.`);
|
|
174
|
+
logHook('progress-tracker', 'SessionStart', 'stale-auto-next', { ageMinutes });
|
|
175
|
+
}
|
|
176
|
+
} catch (_e) {
|
|
177
|
+
// Ignore errors
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
parts.push('\nAvailable commands: /pbr:status, /pbr:plan, /pbr:build, /pbr:review, /pbr:help');
|
|
182
|
+
|
|
183
|
+
return parts.join('\n');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function extractSection(content, heading) {
|
|
187
|
+
const regex = new RegExp(`##\\s+${escapeRegex(heading)}\\s*\\n([\\s\\S]*?)(?=\\n##\\s|$)`);
|
|
188
|
+
const match = content.match(regex);
|
|
189
|
+
if (!match) return null;
|
|
190
|
+
const section = match[1].trim();
|
|
191
|
+
// Return first 5 lines max
|
|
192
|
+
return section.split('\n').slice(0, 5).join('\n');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function escapeRegex(str) {
|
|
196
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function findContinueFiles(dir) {
|
|
200
|
+
const results = [];
|
|
201
|
+
try {
|
|
202
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
203
|
+
for (const entry of entries) {
|
|
204
|
+
const fullPath = path.join(dir, entry.name);
|
|
205
|
+
if (entry.isDirectory()) {
|
|
206
|
+
results.push(...findContinueFiles(fullPath));
|
|
207
|
+
} else if (entry.name.includes('.continue-here')) {
|
|
208
|
+
results.push(path.relative(dir, fullPath));
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
} catch (_e) {
|
|
212
|
+
// Ignore permission errors
|
|
213
|
+
}
|
|
214
|
+
return results;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function countNotes(filePath) {
|
|
218
|
+
try {
|
|
219
|
+
if (!fs.existsSync(filePath)) return 0;
|
|
220
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
221
|
+
const lines = content.split('\n');
|
|
222
|
+
return lines.filter(l => /^- \[/.test(l) && !l.includes('[promoted]')).length;
|
|
223
|
+
} catch (_e) {
|
|
224
|
+
return 0;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
main();
|