all-hands-cli 0.1.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/.allhands/README.md +75 -0
- package/.allhands/agents/compounder.yaml +15 -0
- package/.allhands/agents/coordinator.yaml +17 -0
- package/.allhands/agents/documentor.yaml +15 -0
- package/.allhands/agents/e2e-test-planner.yaml +17 -0
- package/.allhands/agents/emergent.yaml +22 -0
- package/.allhands/agents/executor.yaml +14 -0
- package/.allhands/agents/ideation.yaml +11 -0
- package/.allhands/agents/initiative-steering.yaml +19 -0
- package/.allhands/agents/judge.yaml +13 -0
- package/.allhands/agents/planner.yaml +19 -0
- package/.allhands/agents/pr-reviewer.yaml +15 -0
- package/.allhands/docs.json +5 -0
- package/.allhands/docs.local.json +26 -0
- package/.allhands/flows/COMPOUNDING.md +203 -0
- package/.allhands/flows/COORDINATION.md +89 -0
- package/.allhands/flows/CORE.md +87 -0
- package/.allhands/flows/DOCUMENTATION.md +218 -0
- package/.allhands/flows/E2E_TEST_PLAN_BUILDING.md +140 -0
- package/.allhands/flows/EMERGENT_PLANNING.md +57 -0
- package/.allhands/flows/IDEATION_SCOPING.md +154 -0
- package/.allhands/flows/INITIATIVE_STEERING.md +110 -0
- package/.allhands/flows/JUDGE_REVIEWING.md +79 -0
- package/.allhands/flows/PROMPT_TASK_EXECUTION.md +68 -0
- package/.allhands/flows/PR_REVIEWING.md +43 -0
- package/.allhands/flows/SPEC_PLANNING.md +216 -0
- package/.allhands/flows/harness/WRITING_HARNESS_FLOWS.md +27 -0
- package/.allhands/flows/harness/WRITING_HARNESS_KNOWLEDGE.md +27 -0
- package/.allhands/flows/harness/WRITING_HARNESS_ORCHESTRATION.md +27 -0
- package/.allhands/flows/harness/WRITING_HARNESS_SKILLS.md +27 -0
- package/.allhands/flows/harness/WRITING_HARNESS_TOOLS.md +27 -0
- package/.allhands/flows/harness/WRITING_HARNESS_VALIDATION_TOOLING.md +27 -0
- package/.allhands/flows/shared/CODEBASE_UNDERSTANDING.md +72 -0
- package/.allhands/flows/shared/CREATE_HARNESS_SPEC.md +48 -0
- package/.allhands/flows/shared/CREATE_SPEC.md +41 -0
- package/.allhands/flows/shared/CREATE_VALIDATION_TOOLING_SPEC.md +70 -0
- package/.allhands/flows/shared/DOCUMENTATION_DISCOVERY.md +123 -0
- package/.allhands/flows/shared/DOCUMENTATION_WRITER.md +101 -0
- package/.allhands/flows/shared/EMERGENT_REFINEMENT_ANALYSIS.md +76 -0
- package/.allhands/flows/shared/EXTERNAL_TECH_GUIDANCE.md +97 -0
- package/.allhands/flows/shared/IDEATION_CODEBASE_GROUNDING.md +49 -0
- package/.allhands/flows/shared/PLAN_DEEPENING.md +152 -0
- package/.allhands/flows/shared/PROMPT_TASKS_CURATION.md +113 -0
- package/.allhands/flows/shared/PROMPT_VALIDATION_REVIEW.MD +99 -0
- package/.allhands/flows/shared/QUICK_PREMORTEM.md +70 -0
- package/.allhands/flows/shared/RESEARCH_GUIDANCE.md +38 -0
- package/.allhands/flows/shared/REVIEW_OPTIONS_BREAKDOWN.md +68 -0
- package/.allhands/flows/shared/SKILL_EXTRACTION.md +84 -0
- package/.allhands/flows/shared/SPEC_FLOW_ANALYSIS.md +119 -0
- package/.allhands/flows/shared/TDD_WORKFLOW.md +109 -0
- package/.allhands/flows/shared/UTILIZE_VALIDATION_TOOLING.md +84 -0
- package/.allhands/flows/shared/WRITING_HARNESS_FLOWS.md +11 -0
- package/.allhands/flows/shared/WRITING_HARNESS_MCP_TOOLS.md +84 -0
- package/.allhands/flows/shared/jury/ARCHITECTURE_REVIEW.md +91 -0
- package/.allhands/flows/shared/jury/BEST_PRACTICES_REVIEW.md +80 -0
- package/.allhands/flows/shared/jury/CLAIM_VERIFICATION_REVIEW.md +101 -0
- package/.allhands/flows/shared/jury/EXPECTATIONS_FIT_REVIEW.md +78 -0
- package/.allhands/flows/shared/jury/MAINTAINABILITY_REVIEW.md +110 -0
- package/.allhands/flows/shared/jury/PROMPTS_EXPECTATIONS_FIT.md +74 -0
- package/.allhands/flows/shared/jury/PROMPTS_FLOW_ANALYSIS.md +92 -0
- package/.allhands/flows/shared/jury/PROMPTS_YAGNI.md +78 -0
- package/.allhands/flows/shared/jury/PROMPT_PREMORTEM.md +125 -0
- package/.allhands/flows/shared/jury/SECURITY_REVIEW.md +86 -0
- package/.allhands/flows/shared/jury/YAGNI_REVIEW.md +82 -0
- package/.allhands/flows/wip/DEBUG_INVESTIGATION.md +162 -0
- package/.allhands/flows/wip/MEMORY_RECALL.md +62 -0
- package/.allhands/harness/ah +131 -0
- package/.allhands/harness/package-lock.json +5292 -0
- package/.allhands/harness/package.json +52 -0
- package/.allhands/harness/src/__tests__/e2e/commands.test.ts +307 -0
- package/.allhands/harness/src/__tests__/e2e/event-loop.test.ts +539 -0
- package/.allhands/harness/src/__tests__/e2e/hooks.test.ts +427 -0
- package/.allhands/harness/src/__tests__/e2e/new-initiative-routing.test.ts +137 -0
- package/.allhands/harness/src/__tests__/e2e/run-e2e.ts +109 -0
- package/.allhands/harness/src/__tests__/e2e/specs-type.test.ts +210 -0
- package/.allhands/harness/src/__tests__/e2e/validation-hooks.test.ts +669 -0
- package/.allhands/harness/src/__tests__/e2e/validation-path-consistency.test.ts +354 -0
- package/.allhands/harness/src/__tests__/e2e/validation.test.ts +528 -0
- package/.allhands/harness/src/__tests__/harness/assertions.ts +318 -0
- package/.allhands/harness/src/__tests__/harness/cli-runner.ts +359 -0
- package/.allhands/harness/src/__tests__/harness/fixture.ts +384 -0
- package/.allhands/harness/src/__tests__/harness/hook-runner.ts +411 -0
- package/.allhands/harness/src/__tests__/harness/index.ts +122 -0
- package/.allhands/harness/src/cli.ts +36 -0
- package/.allhands/harness/src/commands/complexity.ts +177 -0
- package/.allhands/harness/src/commands/context7.ts +202 -0
- package/.allhands/harness/src/commands/docs.ts +557 -0
- package/.allhands/harness/src/commands/hooks.ts +24 -0
- package/.allhands/harness/src/commands/index.ts +51 -0
- package/.allhands/harness/src/commands/knowledge.ts +382 -0
- package/.allhands/harness/src/commands/memories.ts +302 -0
- package/.allhands/harness/src/commands/notify.ts +61 -0
- package/.allhands/harness/src/commands/oracle.ts +158 -0
- package/.allhands/harness/src/commands/perplexity.ts +220 -0
- package/.allhands/harness/src/commands/planning.ts +245 -0
- package/.allhands/harness/src/commands/schema.ts +73 -0
- package/.allhands/harness/src/commands/skills.ts +128 -0
- package/.allhands/harness/src/commands/solutions.ts +353 -0
- package/.allhands/harness/src/commands/spawn.ts +158 -0
- package/.allhands/harness/src/commands/specs.ts +532 -0
- package/.allhands/harness/src/commands/tavily.ts +226 -0
- package/.allhands/harness/src/commands/tools.ts +579 -0
- package/.allhands/harness/src/commands/trace.ts +327 -0
- package/.allhands/harness/src/commands/tui.ts +960 -0
- package/.allhands/harness/src/commands/validate.ts +143 -0
- package/.allhands/harness/src/commands/validation-tools.ts +108 -0
- package/.allhands/harness/src/hooks/context.ts +1442 -0
- package/.allhands/harness/src/hooks/enforcement.ts +170 -0
- package/.allhands/harness/src/hooks/index.ts +54 -0
- package/.allhands/harness/src/hooks/lifecycle.ts +229 -0
- package/.allhands/harness/src/hooks/notification.ts +104 -0
- package/.allhands/harness/src/hooks/observability.ts +551 -0
- package/.allhands/harness/src/hooks/session.ts +88 -0
- package/.allhands/harness/src/hooks/shared.ts +815 -0
- package/.allhands/harness/src/hooks/transcript-parser.ts +208 -0
- package/.allhands/harness/src/hooks/validation.ts +617 -0
- package/.allhands/harness/src/lib/__tests__/ctags.test.ts +244 -0
- package/.allhands/harness/src/lib/__tests__/docs-validation.test.ts +344 -0
- package/.allhands/harness/src/lib/__tests__/mcp-runtime.test.ts +190 -0
- package/.allhands/harness/src/lib/__tests__/schema.test.ts +861 -0
- package/.allhands/harness/src/lib/base-command.ts +198 -0
- package/.allhands/harness/src/lib/cli-daemon.ts +343 -0
- package/.allhands/harness/src/lib/compaction.ts +313 -0
- package/.allhands/harness/src/lib/ctags.ts +497 -0
- package/.allhands/harness/src/lib/docs-validation.ts +907 -0
- package/.allhands/harness/src/lib/event-loop.ts +662 -0
- package/.allhands/harness/src/lib/flows.ts +155 -0
- package/.allhands/harness/src/lib/git.ts +276 -0
- package/.allhands/harness/src/lib/knowledge-worker.ts +72 -0
- package/.allhands/harness/src/lib/knowledge.ts +810 -0
- package/.allhands/harness/src/lib/llm.ts +255 -0
- package/.allhands/harness/src/lib/mcp-client.ts +432 -0
- package/.allhands/harness/src/lib/mcp-daemon.ts +486 -0
- package/.allhands/harness/src/lib/mcp-runtime.ts +418 -0
- package/.allhands/harness/src/lib/notification.ts +115 -0
- package/.allhands/harness/src/lib/opencode/index.ts +70 -0
- package/.allhands/harness/src/lib/opencode/profiles.ts +300 -0
- package/.allhands/harness/src/lib/opencode/prompts/codesearch.md +98 -0
- package/.allhands/harness/src/lib/opencode/prompts/knowledge-aggregator.md +67 -0
- package/.allhands/harness/src/lib/opencode/runner.ts +281 -0
- package/.allhands/harness/src/lib/oracle.ts +926 -0
- package/.allhands/harness/src/lib/planning-utils.ts +150 -0
- package/.allhands/harness/src/lib/planning.ts +605 -0
- package/.allhands/harness/src/lib/pr-review.ts +225 -0
- package/.allhands/harness/src/lib/prompts.ts +522 -0
- package/.allhands/harness/src/lib/schema.ts +418 -0
- package/.allhands/harness/src/lib/schemas/agent-profile.ts +141 -0
- package/.allhands/harness/src/lib/schemas/template-vars.ts +138 -0
- package/.allhands/harness/src/lib/session.ts +164 -0
- package/.allhands/harness/src/lib/specs.ts +348 -0
- package/.allhands/harness/src/lib/tldr.ts +829 -0
- package/.allhands/harness/src/lib/tmux.ts +1051 -0
- package/.allhands/harness/src/lib/trace-store.ts +714 -0
- package/.allhands/harness/src/mcp/__tests__/index.test.ts +46 -0
- package/.allhands/harness/src/mcp/_template.ts +47 -0
- package/.allhands/harness/src/mcp/filesystem.ts +33 -0
- package/.allhands/harness/src/mcp/index.ts +69 -0
- package/.allhands/harness/src/mcp/playwright.ts +34 -0
- package/.allhands/harness/src/mcp/xcodebuild.ts +29 -0
- package/.allhands/harness/src/schemas/docs.schema.json +44 -0
- package/.allhands/harness/src/schemas/settings.schema.json +214 -0
- package/.allhands/harness/src/tui/actions.ts +227 -0
- package/.allhands/harness/src/tui/file-viewer-modal.ts +270 -0
- package/.allhands/harness/src/tui/index.ts +1574 -0
- package/.allhands/harness/src/tui/modal.ts +232 -0
- package/.allhands/harness/src/tui/prompts-pane.ts +186 -0
- package/.allhands/harness/src/tui/status-pane.ts +434 -0
- package/.allhands/harness/tsconfig.json +22 -0
- package/.allhands/harness/vitest.config.ts +13 -0
- package/.allhands/pillars.md +33 -0
- package/.allhands/principles.md +88 -0
- package/.allhands/schemas/alignment.yaml +51 -0
- package/.allhands/schemas/documentation.yaml +10 -0
- package/.allhands/schemas/prompt.yaml +92 -0
- package/.allhands/schemas/skill.yaml +34 -0
- package/.allhands/schemas/solution.yaml +131 -0
- package/.allhands/schemas/spec.yaml +67 -0
- package/.allhands/schemas/validation-suite.yaml +49 -0
- package/.allhands/schemas/workflow.yaml +51 -0
- package/.allhands/settings.json +57 -0
- package/.allhands/skills/claude-code-patterns/SKILL.md +60 -0
- package/.allhands/skills/claude-code-patterns/docs/context-hygiene.md +19 -0
- package/.allhands/skills/harness-maintenance/SKILL.md +449 -0
- package/.allhands/skills/harness-maintenance/references/core-architecture.md +187 -0
- package/.allhands/skills/harness-maintenance/references/harness-skills.md +87 -0
- package/.allhands/skills/harness-maintenance/references/knowledge-compounding.md +78 -0
- package/.allhands/skills/harness-maintenance/references/tools-commands-mcp-hooks.md +115 -0
- package/.allhands/skills/harness-maintenance/references/validation-tooling.md +77 -0
- package/.allhands/skills/harness-maintenance/references/writing-flows.md +84 -0
- package/.allhands/validation/browser-automation.md +109 -0
- package/.allhands/validation/xcode-automation.md +195 -0
- package/.allhands/workflows/documentation.md +86 -0
- package/.allhands/workflows/investigation.md +81 -0
- package/.allhands/workflows/milestone.md +91 -0
- package/.allhands/workflows/optimization.md +85 -0
- package/.allhands/workflows/refactor.md +99 -0
- package/.allhands/workflows/triage.md +81 -0
- package/.claude/README.md +1 -0
- package/.claude/agents/explorer.md +10 -0
- package/.claude/agents/researcher.md +11 -0
- package/.claude/agents/task-runner.md +8 -0
- package/.claude/settings.json +231 -0
- package/.env.ai.example +7 -0
- package/.github/workflows/npm-publish.yml +69 -0
- package/.internal.json +45 -0
- package/.tldr/config.json +11 -0
- package/.tldrignore +90 -0
- package/CLAUDE.md +6 -0
- package/README.md +98 -0
- package/bin/sync-cli.js +7552 -0
- package/concerns.md +7 -0
- package/docs/README.md +41 -0
- package/docs/agents/README.md +24 -0
- package/docs/agents/agent-configuration-system.md +86 -0
- package/docs/agents/execution-agents.md +50 -0
- package/docs/agents/knowledge-agents.md +61 -0
- package/docs/agents/orchestration-agent.md +57 -0
- package/docs/agents/planning-agents.md +84 -0
- package/docs/agents/quality-review-agents.md +67 -0
- package/docs/agents/workflow-agent-orchestration.md +69 -0
- package/docs/flows/README.md +44 -0
- package/docs/flows/compounding.md +126 -0
- package/docs/flows/coordination.md +72 -0
- package/docs/flows/core-harness-integration.md +63 -0
- package/docs/flows/documentation-orchestration.md +98 -0
- package/docs/flows/e2e-test-plan-building.md +83 -0
- package/docs/flows/emergent-refinement.md +104 -0
- package/docs/flows/flow-authoring-and-mcp-tools.md +89 -0
- package/docs/flows/judge-reviewing.md +112 -0
- package/docs/flows/plan-deepening-and-research.md +107 -0
- package/docs/flows/plan-review-jury.md +114 -0
- package/docs/flows/pr-reviewing.md +54 -0
- package/docs/flows/prompt-task-execution.md +119 -0
- package/docs/flows/spec-planning.md +162 -0
- package/docs/flows/type-specific-scoping-flows.md +49 -0
- package/docs/flows/validation-and-skills-integration.md +145 -0
- package/docs/flows/wip/wip-flows.md +102 -0
- package/docs/harness/README.md +23 -0
- package/docs/harness/agent-profiles.md +84 -0
- package/docs/harness/cli/README.md +24 -0
- package/docs/harness/cli/cli-entry-and-command-discovery.md +91 -0
- package/docs/harness/cli/docs-command.md +87 -0
- package/docs/harness/cli/knowledge-command.md +91 -0
- package/docs/harness/cli/minor-cli-commands.md +65 -0
- package/docs/harness/cli/oracle-command.md +113 -0
- package/docs/harness/cli/planning-command.md +95 -0
- package/docs/harness/cli/schema-and-validation-commands.md +154 -0
- package/docs/harness/cli/search-commands.md +97 -0
- package/docs/harness/cli/spawn-command.md +136 -0
- package/docs/harness/cli/specs-command.md +102 -0
- package/docs/harness/cli/tools-command.md +122 -0
- package/docs/harness/cli/trace-command.md +122 -0
- package/docs/harness/cli-daemon.md +92 -0
- package/docs/harness/event-loop.md +184 -0
- package/docs/harness/hooks/README.md +15 -0
- package/docs/harness/hooks/context-hooks.md +96 -0
- package/docs/harness/hooks/lifecycle-and-observability-hooks.md +135 -0
- package/docs/harness/hooks/validation-hooks.md +97 -0
- package/docs/harness/test-harness.md +149 -0
- package/docs/harness/tui.md +176 -0
- package/docs/memories.md +20 -0
- package/docs/solutions/agentic-issues/premature-agent-deletion-tui-action-dependency-20260130.md +49 -0
- package/docs/solutions/agentic-issues/ref-anchor-scope-mismatch-skill-references-20260131.md +55 -0
- package/docs/solutions/agentic-issues/tautological-tests-routing-20260131.md +52 -0
- package/docs/solutions/integration_issue/blocktool-output-format-mismatch-hook-runner-20260130.md +52 -0
- package/docs/solutions/integration_issue/dual-validation-path-divergence-schema-20260130.md +66 -0
- package/docs/solutions/security-issues/unsanitized-domain-path-join-20260131.md +52 -0
- package/docs/solutions/test-failures/event-loop-mock-ordering-checkAgentWindows-20260130.md +63 -0
- package/docs/sync-cli/README.md +19 -0
- package/docs/sync-cli/cli-entrypoint-and-commands.md +39 -0
- package/docs/sync-cli/commands/README.md +11 -0
- package/docs/sync-cli/commands/pull-manifest-command.md +36 -0
- package/docs/sync-cli/commands/push-command.md +84 -0
- package/docs/sync-cli/commands/sync-command.md +71 -0
- package/docs/sync-cli/systems/README.md +14 -0
- package/docs/sync-cli/systems/git-and-github-integration.md +49 -0
- package/docs/sync-cli/systems/interactive-ui.md +43 -0
- package/docs/sync-cli/systems/manifest-and-distribution.md +51 -0
- package/docs/sync-cli/systems/path-resolution.md +42 -0
- package/package.json +46 -0
- package/scripts/install-shim.sh +40 -0
- package/scripts/pre-pack.sh +25 -0
- package/specs/harness-maintenance-skill.spec.md +138 -0
- package/specs/roadmap/git-spec-lifecycle-management.spec.md +113 -0
- package/specs/sync-init-flag.spec.md +117 -0
- package/specs/unified-workflow-orchestration.spec.md +250 -0
- package/specs/validation-tooling-practice.spec.md +98 -0
- package/specs/workflow-domain-configuration.spec.md +265 -0
- package/src/commands/pull-manifest.ts +31 -0
- package/src/commands/push.ts +344 -0
- package/src/commands/sync.ts +289 -0
- package/src/lib/constants.ts +10 -0
- package/src/lib/dotfiles.ts +36 -0
- package/src/lib/fs-utils.ts +18 -0
- package/src/lib/gh.ts +40 -0
- package/src/lib/git.ts +63 -0
- package/src/lib/gitignore.ts +167 -0
- package/src/lib/manifest.ts +121 -0
- package/src/lib/marker-sync.ts +39 -0
- package/src/lib/paths.ts +38 -0
- package/src/lib/target-lines.ts +66 -0
- package/src/lib/ui.ts +78 -0
- package/src/sync-cli.ts +120 -0
- package/target-lines.json +23 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompt Management and Picker
|
|
3
|
+
*
|
|
4
|
+
* Handles prompt file operations and the prompt picker algorithm
|
|
5
|
+
* for the execution loop.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { existsSync, readFileSync, writeFileSync, readdirSync } from 'fs';
|
|
9
|
+
import { join, basename } from 'path';
|
|
10
|
+
import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
|
|
11
|
+
import { lockSync, unlockSync } from 'proper-lockfile';
|
|
12
|
+
import { getPlanningPaths } from './planning.js';
|
|
13
|
+
|
|
14
|
+
export type PromptStatus = 'pending' | 'in_progress' | 'done';
|
|
15
|
+
export type PromptPriority = 'high' | 'medium' | 'low';
|
|
16
|
+
|
|
17
|
+
export interface PromptFrontmatter {
|
|
18
|
+
number: number;
|
|
19
|
+
title: string;
|
|
20
|
+
status: PromptStatus;
|
|
21
|
+
dependencies: number[];
|
|
22
|
+
priority: PromptPriority;
|
|
23
|
+
attempts: number;
|
|
24
|
+
commits: string[];
|
|
25
|
+
created: string;
|
|
26
|
+
updated: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface PromptFile {
|
|
30
|
+
path: string;
|
|
31
|
+
filename: string;
|
|
32
|
+
frontmatter: PromptFrontmatter;
|
|
33
|
+
body: string;
|
|
34
|
+
rawContent: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface PickerResult {
|
|
38
|
+
prompt: PromptFile | null;
|
|
39
|
+
reason: string;
|
|
40
|
+
stats: {
|
|
41
|
+
total: number;
|
|
42
|
+
pending: number;
|
|
43
|
+
inProgress: number;
|
|
44
|
+
done: number;
|
|
45
|
+
blocked: number;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const PRIORITY_ORDER: Record<PromptPriority, number> = {
|
|
50
|
+
high: 0,
|
|
51
|
+
medium: 1,
|
|
52
|
+
low: 2,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Execute a function with file locking to prevent race conditions.
|
|
57
|
+
*/
|
|
58
|
+
function withFileLock<T>(filePath: string, fn: () => T): T {
|
|
59
|
+
if (!existsSync(filePath)) {
|
|
60
|
+
// File doesn't exist yet, no locking needed
|
|
61
|
+
return fn();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
lockSync(filePath);
|
|
65
|
+
try {
|
|
66
|
+
return fn();
|
|
67
|
+
} finally {
|
|
68
|
+
try {
|
|
69
|
+
unlockSync(filePath);
|
|
70
|
+
} catch {
|
|
71
|
+
// Ignore unlock errors
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Parse a prompt file
|
|
78
|
+
*/
|
|
79
|
+
export function parsePromptFile(filePath: string): PromptFile | null {
|
|
80
|
+
if (!existsSync(filePath)) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
const rawContent = readFileSync(filePath, 'utf-8');
|
|
86
|
+
const frontmatterMatch = rawContent.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
87
|
+
|
|
88
|
+
if (!frontmatterMatch) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const frontmatter = parseYaml(frontmatterMatch[1]) as PromptFrontmatter;
|
|
93
|
+
const body = frontmatterMatch[2];
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
path: filePath,
|
|
97
|
+
filename: basename(filePath),
|
|
98
|
+
frontmatter,
|
|
99
|
+
body,
|
|
100
|
+
rawContent,
|
|
101
|
+
};
|
|
102
|
+
} catch {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Load all prompt files from the planning directory for a spec
|
|
109
|
+
*/
|
|
110
|
+
export function loadAllPrompts(spec: string, cwd?: string): PromptFile[] {
|
|
111
|
+
const paths = getPlanningPaths(spec, cwd);
|
|
112
|
+
|
|
113
|
+
if (!existsSync(paths.prompts)) {
|
|
114
|
+
return [];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const files = readdirSync(paths.prompts)
|
|
118
|
+
.filter((f) => f.endsWith('.md'))
|
|
119
|
+
.map((f) => join(paths.prompts, f));
|
|
120
|
+
|
|
121
|
+
const prompts: PromptFile[] = [];
|
|
122
|
+
for (const file of files) {
|
|
123
|
+
const prompt = parsePromptFile(file);
|
|
124
|
+
if (prompt) {
|
|
125
|
+
prompts.push(prompt);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return prompts;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Get the next available prompt number for a spec
|
|
134
|
+
* Returns the highest existing prompt number + 1
|
|
135
|
+
*/
|
|
136
|
+
export function getNextPromptNumber(spec: string, cwd?: string): number {
|
|
137
|
+
const prompts = loadAllPrompts(spec, cwd);
|
|
138
|
+
|
|
139
|
+
if (prompts.length === 0) {
|
|
140
|
+
return 1;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const highestNumber = Math.max(...prompts.map((p) => p.frontmatter.number));
|
|
144
|
+
return highestNumber + 1;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Check if a prompt's dependencies are satisfied
|
|
149
|
+
*/
|
|
150
|
+
export function dependenciesSatisfied(
|
|
151
|
+
prompt: PromptFile,
|
|
152
|
+
allPrompts: PromptFile[]
|
|
153
|
+
): boolean {
|
|
154
|
+
const deps = prompt.frontmatter.dependencies || [];
|
|
155
|
+
if (deps.length === 0) return true;
|
|
156
|
+
|
|
157
|
+
const donePromptNumbers = new Set(
|
|
158
|
+
allPrompts
|
|
159
|
+
.filter((p) => p.frontmatter.status === 'done')
|
|
160
|
+
.map((p) => p.frontmatter.number)
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
return deps.every((depNum) => donePromptNumbers.has(depNum));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Pick the next prompt to execute for a spec
|
|
168
|
+
*
|
|
169
|
+
* Algorithm:
|
|
170
|
+
* 1. Filter to pending/in_progress prompts with satisfied dependencies
|
|
171
|
+
* 2. Exclude prompts already being executed (via excludePrompts)
|
|
172
|
+
* 3. Prefer in_progress over pending (resume interrupted work)
|
|
173
|
+
* 4. Sort by priority (high > medium > low)
|
|
174
|
+
* 5. Within same priority, sort by number (lower first)
|
|
175
|
+
*
|
|
176
|
+
* @param spec - The spec/planning key
|
|
177
|
+
* @param cwd - Working directory
|
|
178
|
+
* @param excludePrompts - Prompt numbers to exclude (already being executed)
|
|
179
|
+
*/
|
|
180
|
+
export function pickNextPrompt(
|
|
181
|
+
spec: string,
|
|
182
|
+
cwd?: string,
|
|
183
|
+
excludePrompts: number[] = []
|
|
184
|
+
): PickerResult {
|
|
185
|
+
const prompts = loadAllPrompts(spec, cwd);
|
|
186
|
+
const excludeSet = new Set(excludePrompts);
|
|
187
|
+
|
|
188
|
+
const stats = {
|
|
189
|
+
total: prompts.length,
|
|
190
|
+
pending: 0,
|
|
191
|
+
inProgress: 0,
|
|
192
|
+
done: 0,
|
|
193
|
+
blocked: 0,
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
// Count by status
|
|
197
|
+
for (const p of prompts) {
|
|
198
|
+
switch (p.frontmatter.status) {
|
|
199
|
+
case 'pending':
|
|
200
|
+
stats.pending++;
|
|
201
|
+
break;
|
|
202
|
+
case 'in_progress':
|
|
203
|
+
stats.inProgress++;
|
|
204
|
+
break;
|
|
205
|
+
case 'done':
|
|
206
|
+
stats.done++;
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (prompts.length === 0) {
|
|
212
|
+
return {
|
|
213
|
+
prompt: null,
|
|
214
|
+
reason: 'No prompt files found',
|
|
215
|
+
stats,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Filter to actionable prompts (excluding those already being executed)
|
|
220
|
+
const actionable = prompts.filter((p) => {
|
|
221
|
+
if (p.frontmatter.status === 'done') return false;
|
|
222
|
+
if (excludeSet.has(p.frontmatter.number)) return false;
|
|
223
|
+
if (!dependenciesSatisfied(p, prompts)) {
|
|
224
|
+
stats.blocked++;
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
return true;
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// Recalculate pending count (some may be blocked)
|
|
231
|
+
stats.pending = stats.pending - stats.blocked;
|
|
232
|
+
|
|
233
|
+
if (actionable.length === 0) {
|
|
234
|
+
if (stats.done === stats.total) {
|
|
235
|
+
return {
|
|
236
|
+
prompt: null,
|
|
237
|
+
reason: 'All prompts completed',
|
|
238
|
+
stats,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
// Check if all remaining are either blocked or already being executed
|
|
242
|
+
const beingExecuted = excludePrompts.length;
|
|
243
|
+
if (beingExecuted > 0) {
|
|
244
|
+
return {
|
|
245
|
+
prompt: null,
|
|
246
|
+
reason: `${beingExecuted} prompt(s) in progress, remaining blocked by dependencies`,
|
|
247
|
+
stats,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
return {
|
|
251
|
+
prompt: null,
|
|
252
|
+
reason: 'No actionable prompts (all remaining are blocked by dependencies)',
|
|
253
|
+
stats,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Sort: in_progress first, then by priority, then by number
|
|
258
|
+
actionable.sort((a, b) => {
|
|
259
|
+
// In-progress prompts first (resume interrupted work)
|
|
260
|
+
if (a.frontmatter.status === 'in_progress' && b.frontmatter.status !== 'in_progress') {
|
|
261
|
+
return -1;
|
|
262
|
+
}
|
|
263
|
+
if (b.frontmatter.status === 'in_progress' && a.frontmatter.status !== 'in_progress') {
|
|
264
|
+
return 1;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Then by priority
|
|
268
|
+
const priorityDiff =
|
|
269
|
+
PRIORITY_ORDER[a.frontmatter.priority] - PRIORITY_ORDER[b.frontmatter.priority];
|
|
270
|
+
if (priorityDiff !== 0) return priorityDiff;
|
|
271
|
+
|
|
272
|
+
// Then by number
|
|
273
|
+
return a.frontmatter.number - b.frontmatter.number;
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
const selected = actionable[0];
|
|
277
|
+
const reason =
|
|
278
|
+
selected.frontmatter.status === 'in_progress'
|
|
279
|
+
? `Resuming in-progress prompt ${selected.frontmatter.number}`
|
|
280
|
+
: `Selected prompt ${selected.frontmatter.number} (${selected.frontmatter.priority} priority)`;
|
|
281
|
+
|
|
282
|
+
return {
|
|
283
|
+
prompt: selected,
|
|
284
|
+
reason,
|
|
285
|
+
stats,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Update a prompt file's frontmatter
|
|
291
|
+
*/
|
|
292
|
+
export function updatePromptFrontmatter(
|
|
293
|
+
filePath: string,
|
|
294
|
+
updates: Partial<PromptFrontmatter>
|
|
295
|
+
): PromptFile | null {
|
|
296
|
+
return withFileLock(filePath, () => {
|
|
297
|
+
const prompt = parsePromptFile(filePath);
|
|
298
|
+
if (!prompt) return null;
|
|
299
|
+
|
|
300
|
+
const updatedFrontmatter = {
|
|
301
|
+
...prompt.frontmatter,
|
|
302
|
+
...updates,
|
|
303
|
+
updated: new Date().toISOString(),
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
const newContent = `---
|
|
307
|
+
${stringifyYaml(updatedFrontmatter).trim()}
|
|
308
|
+
---
|
|
309
|
+
${prompt.body}`;
|
|
310
|
+
|
|
311
|
+
writeFileSync(filePath, newContent);
|
|
312
|
+
return parsePromptFile(filePath);
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Mark a prompt as in_progress
|
|
318
|
+
*/
|
|
319
|
+
export function markPromptInProgress(filePath: string): PromptFile | null {
|
|
320
|
+
return updatePromptFrontmatter(filePath, { status: 'in_progress' });
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Mark a prompt as done
|
|
325
|
+
*/
|
|
326
|
+
export function markPromptDone(filePath: string): PromptFile | null {
|
|
327
|
+
return updatePromptFrontmatter(filePath, { status: 'done' });
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Increment prompt attempts counter
|
|
332
|
+
*/
|
|
333
|
+
export function incrementPromptAttempts(filePath: string): PromptFile | null {
|
|
334
|
+
const prompt = parsePromptFile(filePath);
|
|
335
|
+
if (!prompt) return null;
|
|
336
|
+
|
|
337
|
+
return updatePromptFrontmatter(filePath, {
|
|
338
|
+
attempts: (prompt.frontmatter.attempts || 0) + 1,
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Create a new prompt file for a spec
|
|
344
|
+
*/
|
|
345
|
+
export function createPrompt(
|
|
346
|
+
number: number,
|
|
347
|
+
title: string,
|
|
348
|
+
tasks: string[],
|
|
349
|
+
options: {
|
|
350
|
+
dependencies?: number[];
|
|
351
|
+
priority?: PromptPriority;
|
|
352
|
+
acceptanceCriteria?: string[];
|
|
353
|
+
} = {},
|
|
354
|
+
spec: string,
|
|
355
|
+
cwd?: string
|
|
356
|
+
): string {
|
|
357
|
+
const paths = getPlanningPaths(spec, cwd);
|
|
358
|
+
const now = new Date().toISOString();
|
|
359
|
+
|
|
360
|
+
const frontmatter: PromptFrontmatter = {
|
|
361
|
+
number,
|
|
362
|
+
title,
|
|
363
|
+
status: 'pending',
|
|
364
|
+
dependencies: options.dependencies || [],
|
|
365
|
+
priority: options.priority || 'medium',
|
|
366
|
+
attempts: 0,
|
|
367
|
+
commits: [],
|
|
368
|
+
created: now,
|
|
369
|
+
updated: now,
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
const tasksList = tasks.map((t, i) => `${i + 1}. ${t}`).join('\n');
|
|
373
|
+
const criteriaSection = options.acceptanceCriteria
|
|
374
|
+
? `\n## Acceptance Criteria\n\n${options.acceptanceCriteria.map((c) => `- ${c}`).join('\n')}\n`
|
|
375
|
+
: '';
|
|
376
|
+
|
|
377
|
+
const content = `---
|
|
378
|
+
${stringifyYaml(frontmatter).trim()}
|
|
379
|
+
---
|
|
380
|
+
|
|
381
|
+
## Tasks
|
|
382
|
+
|
|
383
|
+
${tasksList}
|
|
384
|
+
${criteriaSection}
|
|
385
|
+
## Progress
|
|
386
|
+
|
|
387
|
+
<!-- Agent-updated section -->
|
|
388
|
+
|
|
389
|
+
`;
|
|
390
|
+
|
|
391
|
+
const slug = title.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, '');
|
|
392
|
+
const filename = `${String(number).padStart(2, '0')}-${slug}.md`;
|
|
393
|
+
const filePath = join(paths.prompts, filename);
|
|
394
|
+
|
|
395
|
+
writeFileSync(filePath, content);
|
|
396
|
+
return filePath;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Get prompt by number for a spec
|
|
401
|
+
*/
|
|
402
|
+
export function getPromptByNumber(
|
|
403
|
+
number: number,
|
|
404
|
+
spec: string,
|
|
405
|
+
cwd?: string
|
|
406
|
+
): PromptFile | null {
|
|
407
|
+
const prompts = loadAllPrompts(spec, cwd);
|
|
408
|
+
return prompts.find((p) => p.frontmatter.number === number) || null;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Append content to a prompt's Progress section
|
|
413
|
+
*
|
|
414
|
+
* Format appended:
|
|
415
|
+
* ### Attempt N (timestamp)
|
|
416
|
+
* **Result**: Continue | Scratch | **Progress**: NN%
|
|
417
|
+
*
|
|
418
|
+
* **Key Learnings**:
|
|
419
|
+
* - Learning 1
|
|
420
|
+
* - Learning 2
|
|
421
|
+
*
|
|
422
|
+
* **Blockers**: Blocker description
|
|
423
|
+
*
|
|
424
|
+
* **Preserved**: `file1.ts`, `file2.ts`
|
|
425
|
+
*/
|
|
426
|
+
export function appendToProgressSection(
|
|
427
|
+
filePath: string,
|
|
428
|
+
content: string
|
|
429
|
+
): PromptFile | null {
|
|
430
|
+
return withFileLock(filePath, () => {
|
|
431
|
+
const prompt = parsePromptFile(filePath);
|
|
432
|
+
if (!prompt) return null;
|
|
433
|
+
|
|
434
|
+
// Find the Progress section
|
|
435
|
+
const progressMarker = '## Progress';
|
|
436
|
+
const progressIndex = prompt.body.indexOf(progressMarker);
|
|
437
|
+
|
|
438
|
+
if (progressIndex === -1) {
|
|
439
|
+
// No Progress section found - append to end
|
|
440
|
+
const newBody = prompt.body + '\n## Progress\n\n' + content + '\n';
|
|
441
|
+
const newContent = `---
|
|
442
|
+
${stringifyYaml(prompt.frontmatter).trim()}
|
|
443
|
+
---
|
|
444
|
+
${newBody}`;
|
|
445
|
+
writeFileSync(filePath, newContent);
|
|
446
|
+
return parsePromptFile(filePath);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Insert content after Progress section header and any existing content
|
|
450
|
+
// Find the next section (## header) or end of file
|
|
451
|
+
const afterProgress = prompt.body.substring(progressIndex + progressMarker.length);
|
|
452
|
+
const nextSectionMatch = afterProgress.match(/\n## [^\n]+/);
|
|
453
|
+
|
|
454
|
+
let insertPoint: number;
|
|
455
|
+
if (nextSectionMatch && nextSectionMatch.index !== undefined) {
|
|
456
|
+
// Insert before the next section
|
|
457
|
+
insertPoint = progressIndex + progressMarker.length + nextSectionMatch.index;
|
|
458
|
+
} else {
|
|
459
|
+
// No next section - append to end
|
|
460
|
+
insertPoint = prompt.body.length;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const newBody =
|
|
464
|
+
prompt.body.substring(0, insertPoint).trimEnd() +
|
|
465
|
+
'\n\n' +
|
|
466
|
+
content +
|
|
467
|
+
'\n' +
|
|
468
|
+
prompt.body.substring(insertPoint);
|
|
469
|
+
|
|
470
|
+
const newContent = `---
|
|
471
|
+
${stringifyYaml(prompt.frontmatter).trim()}
|
|
472
|
+
---
|
|
473
|
+
${newBody}`;
|
|
474
|
+
|
|
475
|
+
writeFileSync(filePath, newContent);
|
|
476
|
+
return parsePromptFile(filePath);
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Increment attempts and return the new attempt number
|
|
482
|
+
* (Alias for incrementPromptAttempts that returns the count)
|
|
483
|
+
*/
|
|
484
|
+
export function incrementAttempts(filePath: string): number {
|
|
485
|
+
const prompt = parsePromptFile(filePath);
|
|
486
|
+
if (!prompt) return 1;
|
|
487
|
+
|
|
488
|
+
const newAttempts = (prompt.frontmatter.attempts || 0) + 1;
|
|
489
|
+
updatePromptFrontmatter(filePath, { attempts: newAttempts });
|
|
490
|
+
return newAttempts;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Add a commit hash to a prompt's commits array
|
|
495
|
+
*
|
|
496
|
+
* Commits are stored in chronological order (oldest first).
|
|
497
|
+
* This tracks all work done on the prompt, including failed attempts.
|
|
498
|
+
*/
|
|
499
|
+
export function addCommitToPrompt(filePath: string, commitHash: string): PromptFile | null {
|
|
500
|
+
const prompt = parsePromptFile(filePath);
|
|
501
|
+
if (!prompt) return null;
|
|
502
|
+
|
|
503
|
+
const commits = prompt.frontmatter.commits || [];
|
|
504
|
+
|
|
505
|
+
// Avoid duplicates
|
|
506
|
+
if (commits.includes(commitHash)) {
|
|
507
|
+
return prompt;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
return updatePromptFrontmatter(filePath, {
|
|
511
|
+
commits: [...commits, commitHash],
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Get all commits for a prompt
|
|
517
|
+
*/
|
|
518
|
+
export function getPromptCommits(filePath: string): string[] {
|
|
519
|
+
const prompt = parsePromptFile(filePath);
|
|
520
|
+
if (!prompt) return [];
|
|
521
|
+
return prompt.frontmatter.commits || [];
|
|
522
|
+
}
|