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,121 @@
|
|
|
1
|
+
import { readFileSync, existsSync, statSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { minimatch, Minimatch } from 'minimatch';
|
|
4
|
+
import { GitignoreFilter } from './gitignore.js';
|
|
5
|
+
|
|
6
|
+
interface InternalData {
|
|
7
|
+
internal?: string[];
|
|
8
|
+
initOnly?: string[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const INTERNAL_FILENAME = '.internal.json';
|
|
12
|
+
|
|
13
|
+
export class Manifest {
|
|
14
|
+
private allhandsRoot: string;
|
|
15
|
+
private internalPath: string;
|
|
16
|
+
private data: InternalData;
|
|
17
|
+
private gitignoreFilter: GitignoreFilter;
|
|
18
|
+
private initOnlyMatchers: { matcher: Minimatch; negated: boolean }[];
|
|
19
|
+
|
|
20
|
+
constructor(allhandsRoot: string) {
|
|
21
|
+
this.allhandsRoot = allhandsRoot;
|
|
22
|
+
this.internalPath = join(allhandsRoot, INTERNAL_FILENAME);
|
|
23
|
+
this.data = this.load();
|
|
24
|
+
this.gitignoreFilter = new GitignoreFilter(allhandsRoot);
|
|
25
|
+
this.initOnlyMatchers = this.initOnlyPatterns.map(p => {
|
|
26
|
+
const negated = p.startsWith('!');
|
|
27
|
+
const pattern = negated ? p.slice(1) : p;
|
|
28
|
+
return { matcher: new Minimatch(pattern, { dot: true }), negated };
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
private load(): InternalData {
|
|
33
|
+
if (!existsSync(this.internalPath)) {
|
|
34
|
+
throw new Error(`Internal config not found: ${this.internalPath}`);
|
|
35
|
+
}
|
|
36
|
+
const content = readFileSync(this.internalPath, 'utf-8');
|
|
37
|
+
return JSON.parse(content);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
get internalPatterns(): string[] {
|
|
41
|
+
return this.data.internal || [];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
get initOnlyPatterns(): string[] {
|
|
45
|
+
return this.data.initOnly || [];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Check if a file is marked as internal (should not be distributed).
|
|
50
|
+
*/
|
|
51
|
+
isInternal(path: string): boolean {
|
|
52
|
+
return this.internalPatterns.some(pattern => minimatch(path, pattern, { dot: true }));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Check if a file is init-only using last-match-wins semantics with negation support.
|
|
57
|
+
* Patterns starting with `!` exempt matching files from being init-only.
|
|
58
|
+
*/
|
|
59
|
+
isInitOnly(path: string): boolean {
|
|
60
|
+
let initOnly = false;
|
|
61
|
+
for (const { matcher, negated } of this.initOnlyMatchers) {
|
|
62
|
+
if (matcher.match(path)) {
|
|
63
|
+
initOnly = !negated;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return initOnly;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Check if a file is gitignored.
|
|
71
|
+
*/
|
|
72
|
+
isGitignored(path: string): boolean {
|
|
73
|
+
return this.gitignoreFilter.isIgnored(path);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Check if a file should be distributed.
|
|
78
|
+
* A file is distributable if it's NOT internal AND NOT gitignored.
|
|
79
|
+
*/
|
|
80
|
+
isDistributable(path: string): boolean {
|
|
81
|
+
return !this.isInternal(path) && !this.isGitignored(path);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Get all distributable files from the allhands root.
|
|
86
|
+
* Returns files that are NOT internal AND NOT gitignored.
|
|
87
|
+
*/
|
|
88
|
+
getDistributableFiles(): Set<string> {
|
|
89
|
+
const allFiles = this.gitignoreFilter.getNonIgnoredFiles();
|
|
90
|
+
const filtered = new Set<string>();
|
|
91
|
+
|
|
92
|
+
for (const file of allFiles) {
|
|
93
|
+
if (!this.isInternal(file)) {
|
|
94
|
+
filtered.add(file);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return filtered;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Compare two files byte-by-byte.
|
|
104
|
+
*/
|
|
105
|
+
export function filesAreDifferent(file1: string, file2: string): boolean {
|
|
106
|
+
if (!existsSync(file1) || !existsSync(file2)) {
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const stat1 = statSync(file1);
|
|
111
|
+
const stat2 = statSync(file2);
|
|
112
|
+
|
|
113
|
+
if (stat1.size !== stat2.size) {
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const content1 = readFileSync(file1);
|
|
118
|
+
const content2 = readFileSync(file2);
|
|
119
|
+
|
|
120
|
+
return !content1.equals(content2);
|
|
121
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Ensure a line exists somewhere in a file.
|
|
5
|
+
* If file doesn't exist, create it with just that line.
|
|
6
|
+
* Appends to end of file if line doesn't already exist.
|
|
7
|
+
* Returns true if changes were made.
|
|
8
|
+
*/
|
|
9
|
+
export function ensureLineInFile(filePath: string, line: string, verbose: boolean = false): boolean {
|
|
10
|
+
let content = '';
|
|
11
|
+
let existed = false;
|
|
12
|
+
|
|
13
|
+
if (existsSync(filePath)) {
|
|
14
|
+
content = readFileSync(filePath, 'utf-8');
|
|
15
|
+
existed = true;
|
|
16
|
+
|
|
17
|
+
// Already has the line?
|
|
18
|
+
if (content.includes(line)) {
|
|
19
|
+
if (verbose) console.log(` ${filePath} already contains: ${line}`);
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Append the line
|
|
25
|
+
const lineWithNewline = line + '\n';
|
|
26
|
+
|
|
27
|
+
if (existed && content.trim()) {
|
|
28
|
+
// Ensure newline separation before appending
|
|
29
|
+
const separator = content.endsWith('\n') ? '' : '\n';
|
|
30
|
+
content = content + separator + lineWithNewline;
|
|
31
|
+
} else {
|
|
32
|
+
content = lineWithNewline;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
writeFileSync(filePath, content);
|
|
36
|
+
if (verbose) console.log(existed ? ` Updated ${filePath}` : ` Created ${filePath}`);
|
|
37
|
+
|
|
38
|
+
return true;
|
|
39
|
+
}
|
package/src/lib/paths.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { existsSync } from 'fs';
|
|
2
|
+
import { dirname, resolve } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
|
|
5
|
+
export const UPSTREAM_REPO = 'kalem-edlin/all-hands';
|
|
6
|
+
export const UPSTREAM_OWNER = 'kalem-edlin';
|
|
7
|
+
|
|
8
|
+
export function getAllhandsRoot(): string {
|
|
9
|
+
// 1. Check ALLHANDS_PATH env var (for local dev testing)
|
|
10
|
+
const envPath = process.env.ALLHANDS_PATH;
|
|
11
|
+
if (envPath) {
|
|
12
|
+
const resolved = resolve(envPath);
|
|
13
|
+
if (existsSync(resolved) && existsSync(resolve(resolved, '.internal.json'))) {
|
|
14
|
+
return resolved;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// 2. Fallback: package location (npx usage)
|
|
19
|
+
// When bundled with esbuild, this resolves to bin/cli.js
|
|
20
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
21
|
+
const __dirname = dirname(__filename);
|
|
22
|
+
|
|
23
|
+
// bin/cli.js -> package root (go up one level from bin/)
|
|
24
|
+
let packageRoot = resolve(__dirname, '..');
|
|
25
|
+
if (existsSync(resolve(packageRoot, '.internal.json'))) {
|
|
26
|
+
return packageRoot;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Try going up two levels (in case of dist/lib/paths.js structure)
|
|
30
|
+
packageRoot = resolve(__dirname, '../..');
|
|
31
|
+
if (existsSync(resolve(packageRoot, '.internal.json'))) {
|
|
32
|
+
return packageRoot;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
throw new Error(
|
|
36
|
+
'Could not locate allhands package. Ensure you are running via npx or set ALLHANDS_PATH for local dev.'
|
|
37
|
+
);
|
|
38
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { getAllhandsRoot } from './paths.js';
|
|
4
|
+
import { ensureLineInFile } from './marker-sync.js';
|
|
5
|
+
|
|
6
|
+
export interface TargetLinesConfig {
|
|
7
|
+
[filename: string]: string[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Load the target-lines.json config from the allhands root.
|
|
12
|
+
*/
|
|
13
|
+
export function loadTargetLines(): TargetLinesConfig {
|
|
14
|
+
const configPath = join(getAllhandsRoot(), 'target-lines.json');
|
|
15
|
+
const content = readFileSync(configPath, 'utf-8');
|
|
16
|
+
return JSON.parse(content) as TargetLinesConfig;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Ensure all lines from target-lines.json exist in the target files.
|
|
21
|
+
* Lines are appended if they don't already exist.
|
|
22
|
+
*
|
|
23
|
+
* Special handling for .tldrignore:
|
|
24
|
+
* - First copies target's ORIGINAL .gitignore as base (hard replace)
|
|
25
|
+
* - Then appends target-lines
|
|
26
|
+
*
|
|
27
|
+
* Returns true if any changes were made.
|
|
28
|
+
*/
|
|
29
|
+
export function ensureTargetLines(targetRoot: string, verbose: boolean = false): boolean {
|
|
30
|
+
const config = loadTargetLines();
|
|
31
|
+
let anyChanged = false;
|
|
32
|
+
|
|
33
|
+
// Capture original .gitignore content BEFORE any modifications
|
|
34
|
+
const gitignorePath = join(targetRoot, '.gitignore');
|
|
35
|
+
const originalGitignore = existsSync(gitignorePath)
|
|
36
|
+
? readFileSync(gitignorePath, 'utf-8')
|
|
37
|
+
: '';
|
|
38
|
+
|
|
39
|
+
for (const [filename, lines] of Object.entries(config)) {
|
|
40
|
+
const targetPath = join(targetRoot, filename);
|
|
41
|
+
|
|
42
|
+
if (verbose) console.log(`Ensuring ${filename} has required lines...`);
|
|
43
|
+
|
|
44
|
+
// Special handling for .tldrignore - hard replace with original .gitignore content first
|
|
45
|
+
if (filename === '.tldrignore') {
|
|
46
|
+
if (verbose && originalGitignore) {
|
|
47
|
+
console.log(' Copying original .gitignore content to .tldrignore');
|
|
48
|
+
}
|
|
49
|
+
let baseContent = originalGitignore;
|
|
50
|
+
if (baseContent && !baseContent.endsWith('\n')) {
|
|
51
|
+
baseContent += '\n';
|
|
52
|
+
}
|
|
53
|
+
writeFileSync(targetPath, baseContent);
|
|
54
|
+
anyChanged = true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
for (const line of lines) {
|
|
58
|
+
const updated = ensureLineInFile(targetPath, line, verbose);
|
|
59
|
+
if (updated) {
|
|
60
|
+
anyChanged = true;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return anyChanged;
|
|
66
|
+
}
|
package/src/lib/ui.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { existsSync, readdirSync } from 'fs';
|
|
2
|
+
import { basename, dirname, extname, join } from 'path';
|
|
3
|
+
import * as readline from 'readline';
|
|
4
|
+
|
|
5
|
+
export type ConflictResolution = 'backup' | 'overwrite' | 'cancel';
|
|
6
|
+
|
|
7
|
+
export async function askQuestion(question: string): Promise<string> {
|
|
8
|
+
const rl = readline.createInterface({
|
|
9
|
+
input: process.stdin,
|
|
10
|
+
output: process.stdout,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
return new Promise((resolve) => {
|
|
14
|
+
rl.question(question, (answer) => {
|
|
15
|
+
rl.close();
|
|
16
|
+
resolve(answer.trim());
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function confirm(message: string): Promise<boolean> {
|
|
22
|
+
const answer = await askQuestion(`${message} [y/N]: `);
|
|
23
|
+
return answer.toLowerCase() === 'y';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function askConflictResolution(conflicts: string[]): Promise<ConflictResolution> {
|
|
27
|
+
console.log(`\n${'!'.repeat(60)}`);
|
|
28
|
+
console.log('CONFLICTS DETECTED - The following files differ from source:');
|
|
29
|
+
console.log(`${'!'.repeat(60)}`);
|
|
30
|
+
for (const f of conflicts.sort()) {
|
|
31
|
+
console.log(` → ${f}`);
|
|
32
|
+
}
|
|
33
|
+
console.log();
|
|
34
|
+
console.log('How would you like to handle these conflicts?');
|
|
35
|
+
console.log(' [b] Create backups (file.backup_N.ext) and overwrite');
|
|
36
|
+
console.log(' [o] Overwrite all (lose local changes)');
|
|
37
|
+
console.log(' [c] Cancel (make no changes)');
|
|
38
|
+
console.log();
|
|
39
|
+
|
|
40
|
+
while (true) {
|
|
41
|
+
const answer = await askQuestion('Choice [b/o/c]: ');
|
|
42
|
+
switch (answer.toLowerCase()) {
|
|
43
|
+
case 'b':
|
|
44
|
+
return 'backup';
|
|
45
|
+
case 'o':
|
|
46
|
+
return 'overwrite';
|
|
47
|
+
case 'c':
|
|
48
|
+
return 'cancel';
|
|
49
|
+
default:
|
|
50
|
+
console.log('Please enter b, o, or c');
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function escapeRegex(str: string): string {
|
|
56
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function getNextBackupPath(filePath: string): string {
|
|
60
|
+
const dir = dirname(filePath);
|
|
61
|
+
const ext = extname(filePath);
|
|
62
|
+
const base = basename(filePath, ext);
|
|
63
|
+
|
|
64
|
+
let n = 1;
|
|
65
|
+
if (existsSync(dir)) {
|
|
66
|
+
const files = readdirSync(dir);
|
|
67
|
+
const backupPattern = new RegExp(`^${escapeRegex(base)}\\.backup_(\\d+)${escapeRegex(ext)}$`);
|
|
68
|
+
for (const file of files) {
|
|
69
|
+
const match = file.match(backupPattern);
|
|
70
|
+
if (match) {
|
|
71
|
+
const num = parseInt(match[1], 10);
|
|
72
|
+
if (num >= n) n = num + 1;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return join(dir, `${base}.backup_${n}${ext}`);
|
|
78
|
+
}
|
package/src/sync-cli.ts
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import yargs from 'yargs';
|
|
2
|
+
import { hideBin } from 'yargs/helpers';
|
|
3
|
+
import { cmdSync } from './commands/sync.js';
|
|
4
|
+
import { cmdPullManifest } from './commands/pull-manifest.js';
|
|
5
|
+
import { cmdPush } from './commands/push.js';
|
|
6
|
+
import { checkGitInstalled } from './lib/git.js';
|
|
7
|
+
import { createRequire } from 'module';
|
|
8
|
+
|
|
9
|
+
const require = createRequire(import.meta.url);
|
|
10
|
+
const pkg = require('../package.json');
|
|
11
|
+
const VERSION = pkg.version;
|
|
12
|
+
|
|
13
|
+
// Sync command handler for reuse
|
|
14
|
+
const syncHandler = async (argv: { target?: string; yes?: boolean; init?: boolean }) => {
|
|
15
|
+
const code = await cmdSync(argv.target || '.', argv.yes || false, argv.init || false);
|
|
16
|
+
process.exit(code);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// Sync command builder for reuse
|
|
20
|
+
const syncBuilder = (yargs: yargs.Argv) => {
|
|
21
|
+
return yargs
|
|
22
|
+
.positional('target', {
|
|
23
|
+
describe: 'Target repository path (defaults to current directory)',
|
|
24
|
+
type: 'string',
|
|
25
|
+
default: '.',
|
|
26
|
+
})
|
|
27
|
+
.option('yes', {
|
|
28
|
+
alias: 'y',
|
|
29
|
+
type: 'boolean',
|
|
30
|
+
describe: 'Skip confirmation prompts',
|
|
31
|
+
default: false,
|
|
32
|
+
})
|
|
33
|
+
.option('init', {
|
|
34
|
+
type: 'boolean',
|
|
35
|
+
describe: 'Include init-only files (for first-time setup)',
|
|
36
|
+
default: false,
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
async function main() {
|
|
41
|
+
// Check dependencies
|
|
42
|
+
if (!checkGitInstalled()) {
|
|
43
|
+
console.error('Error: git is not installed. Please install git first.');
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const argv = await yargs(hideBin(process.argv))
|
|
48
|
+
.scriptName('all-hands')
|
|
49
|
+
.version(VERSION)
|
|
50
|
+
.usage('$0 <command> [options]')
|
|
51
|
+
.command(
|
|
52
|
+
'sync [target]',
|
|
53
|
+
'Initialize or update allhands in target repo',
|
|
54
|
+
syncBuilder,
|
|
55
|
+
syncHandler
|
|
56
|
+
)
|
|
57
|
+
.command(
|
|
58
|
+
'pull-manifest',
|
|
59
|
+
'Create sync config for push customization',
|
|
60
|
+
() => {},
|
|
61
|
+
async () => {
|
|
62
|
+
const code = await cmdPullManifest();
|
|
63
|
+
process.exit(code);
|
|
64
|
+
}
|
|
65
|
+
)
|
|
66
|
+
.command(
|
|
67
|
+
'push',
|
|
68
|
+
'Create PR to upstream with local changes',
|
|
69
|
+
(yargs) => {
|
|
70
|
+
return yargs
|
|
71
|
+
.option('include', {
|
|
72
|
+
alias: 'i',
|
|
73
|
+
type: 'array',
|
|
74
|
+
describe: 'Additional files/patterns to include',
|
|
75
|
+
default: [],
|
|
76
|
+
})
|
|
77
|
+
.option('exclude', {
|
|
78
|
+
alias: 'e',
|
|
79
|
+
type: 'array',
|
|
80
|
+
describe: 'Files/patterns to exclude',
|
|
81
|
+
default: [],
|
|
82
|
+
})
|
|
83
|
+
.option('dry-run', {
|
|
84
|
+
type: 'boolean',
|
|
85
|
+
describe: 'Preview without creating PR',
|
|
86
|
+
default: false,
|
|
87
|
+
})
|
|
88
|
+
.option('title', {
|
|
89
|
+
alias: 't',
|
|
90
|
+
type: 'string',
|
|
91
|
+
describe: 'PR title (skips prompt)',
|
|
92
|
+
})
|
|
93
|
+
.option('body', {
|
|
94
|
+
alias: 'b',
|
|
95
|
+
type: 'string',
|
|
96
|
+
describe: 'PR body (skips prompt)',
|
|
97
|
+
});
|
|
98
|
+
},
|
|
99
|
+
async (argv) => {
|
|
100
|
+
const code = await cmdPush(
|
|
101
|
+
argv.include as string[],
|
|
102
|
+
argv.exclude as string[],
|
|
103
|
+
argv.dryRun as boolean,
|
|
104
|
+
argv.title as string | undefined,
|
|
105
|
+
argv.body as string | undefined
|
|
106
|
+
);
|
|
107
|
+
process.exit(code);
|
|
108
|
+
}
|
|
109
|
+
)
|
|
110
|
+
.demandCommand(1, 'Please specify a command')
|
|
111
|
+
.strict()
|
|
112
|
+
.help()
|
|
113
|
+
.alias('h', 'help')
|
|
114
|
+
.parse();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
main().catch((err) => {
|
|
118
|
+
console.error('Error:', err.message);
|
|
119
|
+
process.exit(1);
|
|
120
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"CLAUDE.md": [
|
|
3
|
+
"@.allhands/flows/CORE.md"
|
|
4
|
+
],
|
|
5
|
+
".gitignore": [
|
|
6
|
+
".planning/",
|
|
7
|
+
".env.ai",
|
|
8
|
+
".tldr/*",
|
|
9
|
+
"!.tldr/config.json",
|
|
10
|
+
".reposearch/",
|
|
11
|
+
".allhands-*.backup/",
|
|
12
|
+
".claude-*.backup/",
|
|
13
|
+
".allhands/harness/.cache/",
|
|
14
|
+
"test_plan.md",
|
|
15
|
+
"problems.md"
|
|
16
|
+
],
|
|
17
|
+
".tldrignore": [
|
|
18
|
+
".reposearch/",
|
|
19
|
+
".planning/",
|
|
20
|
+
"docs/",
|
|
21
|
+
".allhands/"
|
|
22
|
+
]
|
|
23
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "node",
|
|
6
|
+
"lib": ["ES2022"],
|
|
7
|
+
"outDir": "./dist",
|
|
8
|
+
"rootDir": "./src",
|
|
9
|
+
"strict": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"declaration": true,
|
|
14
|
+
"declarationMap": true,
|
|
15
|
+
"sourceMap": true,
|
|
16
|
+
"resolveJsonModule": true
|
|
17
|
+
},
|
|
18
|
+
"include": ["src/**/*"],
|
|
19
|
+
"exclude": ["node_modules", "dist", "bin"]
|
|
20
|
+
}
|