@kal-elsam/kairo-runtime 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/README.md +937 -0
- package/bin/harness.js +11 -0
- package/bin/kairo-runtime.js +11 -0
- package/bin/kairo.js +11 -0
- package/global-template/components/catalog.json +25 -0
- package/global-template/components/orchestrator/orchestrator.md +55 -0
- package/global-template/components/sdd-core/handoff.md +38 -0
- package/global-template/components/sdd-core/spec-sizing.md +38 -0
- package/global-template/components/sdd-core/workflow.md +31 -0
- package/global-template/core/orchestrator.md +55 -0
- package/package.json +64 -0
- package/prompts/HARNESS_ENFORCEMENT_UPGRADE.md +11 -0
- package/prompts/HARNESS_ENTERPRISE.md +33 -0
- package/prompts/HARNESS_INSTALLER_MASTER.md +853 -0
- package/prompts/HARNESS_MINIMAL.md +30 -0
- package/prompts/HARNESS_SPEC_SIZING_UPGRADE.md +41 -0
- package/prompts/HARNESS_STANDARD.md +35 -0
- package/prompts/HARNESS_UNIVERSAL_PARITY.md +56 -0
- package/repo-template/.claude/agents/adapter-parity-reviewer.md +17 -0
- package/repo-template/.claude/agents/architect.md +17 -0
- package/repo-template/.claude/agents/eval-engineer.md +17 -0
- package/repo-template/.claude/agents/reviewer.md +17 -0
- package/repo-template/.claude/agents/sdd-reviewer.md +17 -0
- package/repo-template/.claude/agents/sdd-spec-writer.md +17 -0
- package/repo-template/.claude/skills/checkpoint/SKILL.md +13 -0
- package/repo-template/.claude/skills/eval-design/SKILL.md +3 -0
- package/repo-template/.claude/skills/evals/SKILL.md +13 -0
- package/repo-template/.claude/skills/loop-engineering/SKILL.md +16 -0
- package/repo-template/.claude/skills/maintainability-review/SKILL.md +3 -0
- package/repo-template/.claude/skills/quality-gate-review/SKILL.md +3 -0
- package/repo-template/.claude/skills/sdd/SKILL.md +13 -0
- package/repo-template/.claude/skills/spec-complexity-classifier/SKILL.md +10 -0
- package/repo-template/.claude/skills/spec-escalation-review/SKILL.md +10 -0
- package/repo-template/.claude/skills/spec-intake/SKILL.md +10 -0
- package/repo-template/.claude/skills/tdd/SKILL.md +13 -0
- package/repo-template/.claude/skills/trust-review/SKILL.md +3 -0
- package/repo-template/.codex/skills/adapter-parity/SKILL.md +29 -0
- package/repo-template/.codex/skills/checkpoint/SKILL.md +36 -0
- package/repo-template/.codex/skills/eval-design/SKILL.md +3 -0
- package/repo-template/.codex/skills/evals/SKILL.md +28 -0
- package/repo-template/.codex/skills/loop-engineering/SKILL.md +24 -0
- package/repo-template/.codex/skills/maintainability-review/SKILL.md +3 -0
- package/repo-template/.codex/skills/quality-gate-review/SKILL.md +3 -0
- package/repo-template/.codex/skills/sdd/SKILL.md +30 -0
- package/repo-template/.codex/skills/spec-complexity-classifier/SKILL.md +10 -0
- package/repo-template/.codex/skills/spec-escalation-review/SKILL.md +10 -0
- package/repo-template/.codex/skills/spec-intake/SKILL.md +10 -0
- package/repo-template/.codex/skills/tdd/SKILL.md +27 -0
- package/repo-template/.codex/skills/trust-review/SKILL.md +3 -0
- package/repo-template/.cursor/agents/adapter-parity-reviewer.md +29 -0
- package/repo-template/.cursor/agents/architect.md +27 -0
- package/repo-template/.cursor/agents/context-engineer.md +27 -0
- package/repo-template/.cursor/agents/debugger.md +27 -0
- package/repo-template/.cursor/agents/eval-engineer.md +27 -0
- package/repo-template/.cursor/agents/harness-orchestrator.md +27 -0
- package/repo-template/.cursor/agents/loop-controller.md +17 -0
- package/repo-template/.cursor/agents/loop-debugger.md +16 -0
- package/repo-template/.cursor/agents/reviewer.md +27 -0
- package/repo-template/.cursor/agents/sdd/sdd-acceptance-reviewer.md +31 -0
- package/repo-template/.cursor/agents/sdd/sdd-implementation-guardian.md +31 -0
- package/repo-template/.cursor/agents/sdd/sdd-intake-analyst.md +31 -0
- package/repo-template/.cursor/agents/sdd/sdd-orchestrator.md +31 -0
- package/repo-template/.cursor/agents/sdd/sdd-spec-reviewer.md +31 -0
- package/repo-template/.cursor/agents/sdd/sdd-spec-writer.md +31 -0
- package/repo-template/.cursor/agents/sdd/sdd-task-breakdown.md +31 -0
- package/repo-template/.cursor/agents/sdd/sdd-technical-planner.md +31 -0
- package/repo-template/.cursor/agents/sdd/sdd-test-planner.md +31 -0
- package/repo-template/.cursor/agents/spec-intake-analyst.md +14 -0
- package/repo-template/.cursor/agents/test-engineer.md +27 -0
- package/repo-template/.cursor/commands/adapter-parity.md +34 -0
- package/repo-template/.cursor/commands/checkpoint.md +30 -0
- package/repo-template/.cursor/commands/debug.md +13 -0
- package/repo-template/.cursor/commands/init-eval.md +12 -0
- package/repo-template/.cursor/commands/init-feature.md +12 -0
- package/repo-template/.cursor/commands/init-harness.md +13 -0
- package/repo-template/.cursor/commands/loop-debug.md +3 -0
- package/repo-template/.cursor/commands/loop-review.md +3 -0
- package/repo-template/.cursor/commands/loop-run.md +5 -0
- package/repo-template/.cursor/commands/review.md +21 -0
- package/repo-template/.cursor/commands/sdd-intake.md +18 -0
- package/repo-template/.cursor/commands/sdd-plan.md +18 -0
- package/repo-template/.cursor/commands/sdd-spec.md +18 -0
- package/repo-template/.cursor/commands/sdd-tasks.md +18 -0
- package/repo-template/.cursor/commands/sdd-validate.md +18 -0
- package/repo-template/.cursor/commands/spec-escalate.md +9 -0
- package/repo-template/.cursor/commands/spec-intake.md +10 -0
- package/repo-template/.cursor/commands/spec-size.md +21 -0
- package/repo-template/.cursor/rules/adapter-parity.mdc +20 -0
- package/repo-template/.cursor/rules/api.mdc +12 -0
- package/repo-template/.cursor/rules/core.mdc +21 -0
- package/repo-template/.cursor/rules/enforcement.mdc +16 -0
- package/repo-template/.cursor/rules/evals.mdc +10 -0
- package/repo-template/.cursor/rules/git.mdc +19 -0
- package/repo-template/.cursor/rules/harness.mdc +14 -0
- package/repo-template/.cursor/rules/loops.mdc +17 -0
- package/repo-template/.cursor/rules/testing.mdc +13 -0
- package/repo-template/.cursor/rules/ui.mdc +13 -0
- package/repo-template/.cursor/skills/api-design/SKILL.md +23 -0
- package/repo-template/.cursor/skills/architecture/SKILL.md +23 -0
- package/repo-template/.cursor/skills/code-review/SKILL.md +23 -0
- package/repo-template/.cursor/skills/context-graph/SKILL.md +23 -0
- package/repo-template/.cursor/skills/debugging/SKILL.md +23 -0
- package/repo-template/.cursor/skills/evals/SKILL.md +23 -0
- package/repo-template/.cursor/skills/git-workflow/SKILL.md +23 -0
- package/repo-template/.cursor/skills/testing/SKILL.md +23 -0
- package/repo-template/.cursor/skills/ui-design/SKILL.md +23 -0
- package/repo-template/.gentle-ai/README.md +37 -0
- package/repo-template/.gentle-ai/loops/README.md +27 -0
- package/repo-template/.gentle-ai/loops/sdd-tdd-repair.loop.md +27 -0
- package/repo-template/.gentle-ai/model-routing.md +25 -0
- package/repo-template/.github/copilot-instructions.md +37 -0
- package/repo-template/.github/dependabot.yml +11 -0
- package/repo-template/.github/instructions/harness.instructions.md +11 -0
- package/repo-template/.github/workflows/harness-quality-gate.yml +67 -0
- package/repo-template/.github/workflows/harness-security-gate.yml +32 -0
- package/repo-template/.harness/spec-sizing.json +33 -0
- package/repo-template/.opencode/README.md +71 -0
- package/repo-template/.opencode/agents/adapter-parity-reviewer.md +23 -0
- package/repo-template/.opencode/agents/architect.md +38 -0
- package/repo-template/.opencode/agents/checkpoint-reviewer.md +38 -0
- package/repo-template/.opencode/agents/eval-engineer.md +38 -0
- package/repo-template/.opencode/agents/loop-controller.md +38 -0
- package/repo-template/.opencode/agents/loop-debugger.md +38 -0
- package/repo-template/.opencode/agents/model-router.md +38 -0
- package/repo-template/.opencode/agents/sdd-orchestrator.md +38 -0
- package/repo-template/.opencode/agents/sdd-spec-reviewer.md +38 -0
- package/repo-template/.opencode/agents/spec-intake-analyst.md +19 -0
- package/repo-template/.opencode/agents/tdd-engineer.md +38 -0
- package/repo-template/.opencode/commands/checkpoint.md +3 -0
- package/repo-template/.opencode/commands/eval-design.md +3 -0
- package/repo-template/.opencode/commands/maintainability-review.md +3 -0
- package/repo-template/.opencode/commands/quality-gate.md +3 -0
- package/repo-template/.opencode/commands/spec-escalate.md +9 -0
- package/repo-template/.opencode/commands/spec-intake.md +10 -0
- package/repo-template/.opencode/commands/spec-size.md +21 -0
- package/repo-template/.opencode/commands/trust-review.md +3 -0
- package/repo-template/.opencode/loops/ai-behavior-improvement.loop.md +15 -0
- package/repo-template/.opencode/loops/bug-repair.loop.md +16 -0
- package/repo-template/.opencode/loops/documentation-sync.loop.md +13 -0
- package/repo-template/.opencode/loops/feature-development.loop.md +25 -0
- package/repo-template/.opencode/skills/spec-complexity-classifier/SKILL.md +10 -0
- package/repo-template/.opencode/skills/spec-escalation-review/SKILL.md +10 -0
- package/repo-template/.opencode/skills/spec-intake/SKILL.md +10 -0
- package/repo-template/.pi/README.md +21 -0
- package/repo-template/.pi/prompts/checkpoint.md +11 -0
- package/repo-template/.pi/prompts/loop-run.md +10 -0
- package/repo-template/.pi/prompts/sdd-plan.md +11 -0
- package/repo-template/.pi/prompts/sdd-spec.md +13 -0
- package/repo-template/.pi/prompts/spec-escalate.md +9 -0
- package/repo-template/.pi/prompts/spec-intake.md +10 -0
- package/repo-template/.pi/prompts/spec-size.md +21 -0
- package/repo-template/.pi/skills/adapter-parity/SKILL.md +7 -0
- package/repo-template/.pi/skills/checkpoint/SKILL.md +7 -0
- package/repo-template/.pi/skills/eval-design/SKILL.md +3 -0
- package/repo-template/.pi/skills/evals/SKILL.md +7 -0
- package/repo-template/.pi/skills/loop-engineering/SKILL.md +7 -0
- package/repo-template/.pi/skills/maintainability-review/SKILL.md +3 -0
- package/repo-template/.pi/skills/quality-gate-review/SKILL.md +3 -0
- package/repo-template/.pi/skills/sdd/SKILL.md +7 -0
- package/repo-template/.pi/skills/spec-complexity-classifier/SKILL.md +10 -0
- package/repo-template/.pi/skills/spec-escalation-review/SKILL.md +10 -0
- package/repo-template/.pi/skills/spec-intake/SKILL.md +10 -0
- package/repo-template/.pi/skills/tdd/SKILL.md +7 -0
- package/repo-template/.pi/skills/trust-review/SKILL.md +3 -0
- package/repo-template/AGENT.md +8 -0
- package/repo-template/AGENTS.md +165 -0
- package/repo-template/CLAUDE.md +9 -0
- package/repo-template/GEMINI.md +9 -0
- package/repo-template/docs/ai/adapter-parity.md +135 -0
- package/repo-template/docs/ai/agent-workflow.md +45 -0
- package/repo-template/docs/ai/api.md +28 -0
- package/repo-template/docs/ai/architecture.md +53 -0
- package/repo-template/docs/ai/context-budget.md +54 -0
- package/repo-template/docs/ai/context-graph.md +39 -0
- package/repo-template/docs/ai/conventions.md +34 -0
- package/repo-template/docs/ai/decision-log.md +5 -0
- package/repo-template/docs/ai/enforcement.md +63 -0
- package/repo-template/docs/ai/eval-strategy.md +42 -0
- package/repo-template/docs/ai/evals.md +44 -0
- package/repo-template/docs/ai/git-workflow.md +35 -0
- package/repo-template/docs/ai/governance.md +61 -0
- package/repo-template/docs/ai/harness.md +49 -0
- package/repo-template/docs/ai/installer-cli.md +34 -0
- package/repo-template/docs/ai/loop-log.md +5 -0
- package/repo-template/docs/ai/loop-observability.md +57 -0
- package/repo-template/docs/ai/loop-policy.md +86 -0
- package/repo-template/docs/ai/loops.md +95 -0
- package/repo-template/docs/ai/maintainability-gates.md +26 -0
- package/repo-template/docs/ai/memory.md +71 -0
- package/repo-template/docs/ai/model-policy.md +105 -0
- package/repo-template/docs/ai/observability-runtime.md +48 -0
- package/repo-template/docs/ai/provider-routing.md +138 -0
- package/repo-template/docs/ai/quality-gates.md +48 -0
- package/repo-template/docs/ai/rollback-runtime.md +25 -0
- package/repo-template/docs/ai/security.md +12 -0
- package/repo-template/docs/ai/spec-driven-development.md +124 -0
- package/repo-template/docs/ai/spec-escalation.md +44 -0
- package/repo-template/docs/ai/spec-intake.md +46 -0
- package/repo-template/docs/ai/spec-sizing.md +207 -0
- package/repo-template/docs/ai/test-driven-development.md +35 -0
- package/repo-template/docs/ai/testing.md +28 -0
- package/repo-template/docs/ai/tool-adapters.md +186 -0
- package/repo-template/docs/ai/trust-allowlist.md +5 -0
- package/repo-template/docs/ai/trust-policy.md +41 -0
- package/repo-template/docs/ai/ui.md +17 -0
- package/repo-template/docs/skills/adapter-parity-review.md +46 -0
- package/repo-template/docs/skills/api-design.md +37 -0
- package/repo-template/docs/skills/architecture.md +37 -0
- package/repo-template/docs/skills/code-review.md +37 -0
- package/repo-template/docs/skills/context-graph.md +38 -0
- package/repo-template/docs/skills/debugging.md +37 -0
- package/repo-template/docs/skills/eval-design.md +12 -0
- package/repo-template/docs/skills/evals.md +37 -0
- package/repo-template/docs/skills/git-workflow.md +37 -0
- package/repo-template/docs/skills/look-trust-review.md +12 -0
- package/repo-template/docs/skills/loop-debugging.md +16 -0
- package/repo-template/docs/skills/loop-design.md +39 -0
- package/repo-template/docs/skills/loop-retrospective.md +15 -0
- package/repo-template/docs/skills/loop-review.md +16 -0
- package/repo-template/docs/skills/maintainability-review.md +12 -0
- package/repo-template/docs/skills/model-selection.md +36 -0
- package/repo-template/docs/skills/quality-gate-review.md +12 -0
- package/repo-template/docs/skills/spec-complexity-classifier.md +41 -0
- package/repo-template/docs/skills/spec-escalation-review.md +34 -0
- package/repo-template/docs/skills/spec-intake.md +25 -0
- package/repo-template/docs/skills/testing.md +37 -0
- package/repo-template/docs/skills/tool-adapter-sync.md +36 -0
- package/repo-template/docs/skills/ui-design.md +37 -0
- package/repo-template/docs/specs/README.md +9 -0
- package/repo-template/docs/specs/templates/basic-spec.md +41 -0
- package/repo-template/docs/specs/templates/complex-spec.md +145 -0
- package/repo-template/docs/specs/templates/standard-spec.md +58 -0
- package/repo-template/evals/README.md +11 -0
- package/repo-template/evals/golden/sample-golden.json +12 -0
- package/repo-template/evals/loop-regression/README.md +11 -0
- package/repo-template/evals/regression/sample-regression.json +12 -0
- package/repo-template/evals/schema/sample-schema.json +12 -0
- package/repo-template/evals/tool-calls/sample-tool-calls.json +12 -0
- package/repo-template/opencode.json.sample +29 -0
- package/repo-template/scripts/harness/backup.mjs +7 -0
- package/repo-template/scripts/harness/doctor.mjs +20 -0
- package/repo-template/scripts/harness/run-evals.mjs +27 -0
- package/repo-template/setup-agent-links.sh +54 -0
- package/scripts/check-published-release.mjs +127 -0
- package/scripts/check-release-commit.mjs +32 -0
- package/scripts/install.sh +225 -0
- package/scripts/installer-smoke-test.sh +155 -0
- package/scripts/lib/attribution-guard.mjs +59 -0
- package/scripts/registry-smoke-test.sh +149 -0
- package/scripts/smoke-test.sh +197 -0
- package/scripts/ux-smoke-test.sh +140 -0
- package/src/cli.js +625 -0
- package/src/doctor.js +68 -0
- package/src/global/adapter-context.js +27 -0
- package/src/global/adapter-matrix.js +38 -0
- package/src/global/adapters/claude.js +8 -0
- package/src/global/adapters/codex.js +8 -0
- package/src/global/adapters/cursor.js +8 -0
- package/src/global/adapters/opencode.js +8 -0
- package/src/global/agents.js +7 -0
- package/src/global/apply-confirmation.js +62 -0
- package/src/global/backups.js +54 -0
- package/src/global/brand/cli.js +52 -0
- package/src/global/brand/index.js +85 -0
- package/src/global/clack/setup-preview.js +48 -0
- package/src/global/clack/setup-wizard-constants.js +6 -0
- package/src/global/clack/setup-wizard.js +207 -0
- package/src/global/clack/theme.js +145 -0
- package/src/global/component-authoring.js +121 -0
- package/src/global/component-builders.js +43 -0
- package/src/global/component-distribution.js +270 -0
- package/src/global/component-installer.js +106 -0
- package/src/global/component-paths.js +22 -0
- package/src/global/component-registry.js +89 -0
- package/src/global/components/orchestrator.js +14 -0
- package/src/global/components/sdd-core.js +17 -0
- package/src/global/diff.js +233 -0
- package/src/global/drift.js +264 -0
- package/src/global/explain.js +144 -0
- package/src/global/global-cli.js +1145 -0
- package/src/global/global-doctor.js +67 -0
- package/src/global/global-installer.js +284 -0
- package/src/global/history.js +313 -0
- package/src/global/ink/run-setup-ink.js +77 -0
- package/src/global/ink/setup-app.js +278 -0
- package/src/global/ink/setup-routing.js +51 -0
- package/src/global/ink/setup-state.js +180 -0
- package/src/global/ink/terminal.js +13 -0
- package/src/global/json-output.js +22 -0
- package/src/global/load-component-catalog.js +41 -0
- package/src/global/load-workspace-component-catalog.js +123 -0
- package/src/global/managed-body.js +28 -0
- package/src/global/managed-config-adapter.js +134 -0
- package/src/global/managed-section.js +73 -0
- package/src/global/npm-registry.js +25 -0
- package/src/global/paths.js +22 -0
- package/src/global/policy.js +344 -0
- package/src/global/preflight.js +65 -0
- package/src/global/registry.js +79 -0
- package/src/global/report.js +110 -0
- package/src/global/rollback.js +126 -0
- package/src/global/setup.js +284 -0
- package/src/global/state-migration.js +65 -0
- package/src/global/state.js +50 -0
- package/src/global/status.js +94 -0
- package/src/global/sync.js +129 -0
- package/src/global/upgrade.js +145 -0
- package/src/harness-files.js +145 -0
- package/src/harness-updater.js +120 -0
- package/src/hash.js +5 -0
- package/src/manifest.js +36 -0
- package/src/project-detection.js +105 -0
- package/src/template-installer.js +72 -0
- package/src/text-template.js +24 -0
- package/src/workspace-cli.js +115 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { updateGlobalHarness } from "./global-installer.js";
|
|
2
|
+
import {
|
|
3
|
+
assertExplicitApplyConsent,
|
|
4
|
+
createReadlinePrompt,
|
|
5
|
+
promptApplyConfirmation,
|
|
6
|
+
shouldPromptApplyConfirmation
|
|
7
|
+
} from "./apply-confirmation.js";
|
|
8
|
+
import { buildDiffReport } from "./diff.js";
|
|
9
|
+
import { harnessHomePaths } from "./paths.js";
|
|
10
|
+
import { printManagedPreflight, shouldShowPreflight, summarizeDiffPreflight } from "./preflight.js";
|
|
11
|
+
import { loadConsentAudit } from "./policy.js";
|
|
12
|
+
import { readGlobalState } from "./state.js";
|
|
13
|
+
import { buildStatusReport } from "./status.js";
|
|
14
|
+
|
|
15
|
+
export async function runHarnessSync({
|
|
16
|
+
packageRoot,
|
|
17
|
+
packageName,
|
|
18
|
+
cliVersion,
|
|
19
|
+
homeDir,
|
|
20
|
+
workspaceRoot = null,
|
|
21
|
+
dryRun = false,
|
|
22
|
+
yes = false,
|
|
23
|
+
confirm = false,
|
|
24
|
+
preflight = true,
|
|
25
|
+
preflightExplicit = false,
|
|
26
|
+
yesExplicit = false,
|
|
27
|
+
confirmExplicit = false,
|
|
28
|
+
json = false,
|
|
29
|
+
interactive = null,
|
|
30
|
+
createPrompt = createReadlinePrompt
|
|
31
|
+
}) {
|
|
32
|
+
const paths = harnessHomePaths(homeDir);
|
|
33
|
+
const state = await readGlobalState(paths.statePath);
|
|
34
|
+
|
|
35
|
+
if (!state) {
|
|
36
|
+
return {
|
|
37
|
+
action: "setup-required",
|
|
38
|
+
wrote: false,
|
|
39
|
+
result: null,
|
|
40
|
+
report: await buildStatusReport(homeDir, { packageRoot, workspaceRoot })
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const preReport = await buildStatusReport(homeDir, { packageRoot, workspaceRoot });
|
|
45
|
+
const needsRepair = preReport.overall === "drift"
|
|
46
|
+
|| preReport.counts.missing > 0
|
|
47
|
+
|| preReport.counts.stale > 0;
|
|
48
|
+
|
|
49
|
+
if (!needsRepair) {
|
|
50
|
+
return {
|
|
51
|
+
action: "noop",
|
|
52
|
+
wrote: false,
|
|
53
|
+
result: null,
|
|
54
|
+
report: preReport
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const applying = !dryRun;
|
|
59
|
+
|
|
60
|
+
assertExplicitApplyConsent({
|
|
61
|
+
applying,
|
|
62
|
+
dryRun,
|
|
63
|
+
json,
|
|
64
|
+
yes,
|
|
65
|
+
confirm,
|
|
66
|
+
noPreflight: !preflight,
|
|
67
|
+
interactive,
|
|
68
|
+
command: "sync"
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
if (shouldShowPreflight({ preflight, dryRun, json, applying })) {
|
|
72
|
+
const diffReport = await buildDiffReport(homeDir, {
|
|
73
|
+
packageRoot,
|
|
74
|
+
packageName,
|
|
75
|
+
cliVersion,
|
|
76
|
+
workspaceRoot
|
|
77
|
+
});
|
|
78
|
+
const consent = await loadConsentAudit(homeDir, {
|
|
79
|
+
yes,
|
|
80
|
+
confirm,
|
|
81
|
+
yesExplicit,
|
|
82
|
+
confirmExplicit,
|
|
83
|
+
preflight,
|
|
84
|
+
preflightExplicit,
|
|
85
|
+
interactive,
|
|
86
|
+
applying,
|
|
87
|
+
dryRun,
|
|
88
|
+
json
|
|
89
|
+
});
|
|
90
|
+
printManagedPreflight({
|
|
91
|
+
command: "sync",
|
|
92
|
+
...summarizeDiffPreflight(diffReport),
|
|
93
|
+
consentSource: consent.consentSource,
|
|
94
|
+
policyProfile: consent.policyProfile
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (shouldPromptApplyConfirmation({ applying, dryRun, json, confirm, interactive })) {
|
|
99
|
+
const approved = await promptApplyConfirmation({ command: "sync", createPrompt });
|
|
100
|
+
if (!approved) {
|
|
101
|
+
return {
|
|
102
|
+
action: "cancelled",
|
|
103
|
+
wrote: false,
|
|
104
|
+
result: null,
|
|
105
|
+
report: preReport
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const result = await updateGlobalHarness({
|
|
111
|
+
packageRoot,
|
|
112
|
+
packageName,
|
|
113
|
+
cliVersion,
|
|
114
|
+
homeDir,
|
|
115
|
+
workspaceRoot,
|
|
116
|
+
dryRun
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const report = dryRun
|
|
120
|
+
? preReport
|
|
121
|
+
: await buildStatusReport(homeDir, { packageRoot, workspaceRoot });
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
action: dryRun ? "plan" : "repaired",
|
|
125
|
+
wrote: !dryRun,
|
|
126
|
+
result,
|
|
127
|
+
report
|
|
128
|
+
};
|
|
129
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { updateGlobalHarness } from "./global-installer.js";
|
|
2
|
+
import {
|
|
3
|
+
assertExplicitApplyConsent,
|
|
4
|
+
createReadlinePrompt,
|
|
5
|
+
promptApplyConfirmation,
|
|
6
|
+
shouldPromptApplyConfirmation
|
|
7
|
+
} from "./apply-confirmation.js";
|
|
8
|
+
import { buildDiffReport } from "./diff.js";
|
|
9
|
+
import { fetchPublishedVersion } from "./npm-registry.js";
|
|
10
|
+
import { harnessHomePaths } from "./paths.js";
|
|
11
|
+
import { printManagedPreflight, shouldShowPreflight, summarizeDiffPreflight } from "./preflight.js";
|
|
12
|
+
import { loadConsentAudit } from "./policy.js";
|
|
13
|
+
import { readGlobalState } from "./state.js";
|
|
14
|
+
import { formatCliCommand } from "./brand/cli.js";
|
|
15
|
+
import { runHarnessSetup } from "./setup.js";
|
|
16
|
+
|
|
17
|
+
export async function runHarnessUpgrade({
|
|
18
|
+
packageRoot,
|
|
19
|
+
packageName,
|
|
20
|
+
cliVersion,
|
|
21
|
+
homeDir,
|
|
22
|
+
workspaceRoot = null,
|
|
23
|
+
dryRun = false,
|
|
24
|
+
yes = false,
|
|
25
|
+
confirm = false,
|
|
26
|
+
preflight = true,
|
|
27
|
+
preflightExplicit = false,
|
|
28
|
+
yesExplicit = false,
|
|
29
|
+
confirmExplicit = false,
|
|
30
|
+
json = false,
|
|
31
|
+
interactive = null,
|
|
32
|
+
createPrompt = createReadlinePrompt,
|
|
33
|
+
fetchVersion = fetchPublishedVersion
|
|
34
|
+
}) {
|
|
35
|
+
if (yes && dryRun) {
|
|
36
|
+
throw new Error("Use either --dry-run or --yes, not both.");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const preview = !yes;
|
|
40
|
+
|
|
41
|
+
const latestVersion = await fetchVersion(packageName);
|
|
42
|
+
const paths = harnessHomePaths(homeDir);
|
|
43
|
+
const state = await readGlobalState(paths.statePath);
|
|
44
|
+
const latestCommand = `npx ${packageName}@latest setup --yes`;
|
|
45
|
+
const previewCommand = `npx ${packageName}@latest setup --dry-run`;
|
|
46
|
+
|
|
47
|
+
let result = null;
|
|
48
|
+
const applying = yes && !dryRun;
|
|
49
|
+
|
|
50
|
+
assertExplicitApplyConsent({
|
|
51
|
+
applying,
|
|
52
|
+
dryRun,
|
|
53
|
+
json,
|
|
54
|
+
yes,
|
|
55
|
+
confirm,
|
|
56
|
+
noPreflight: !preflight,
|
|
57
|
+
interactive,
|
|
58
|
+
command: "upgrade"
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
if (yes && shouldShowPreflight({ preflight, dryRun: false, json, applying: true }) && state) {
|
|
62
|
+
const diffReport = await buildDiffReport(homeDir, {
|
|
63
|
+
packageRoot,
|
|
64
|
+
packageName,
|
|
65
|
+
cliVersion,
|
|
66
|
+
workspaceRoot
|
|
67
|
+
});
|
|
68
|
+
const consent = await loadConsentAudit(homeDir, {
|
|
69
|
+
yes,
|
|
70
|
+
confirm,
|
|
71
|
+
yesExplicit,
|
|
72
|
+
confirmExplicit,
|
|
73
|
+
preflight,
|
|
74
|
+
preflightExplicit,
|
|
75
|
+
interactive,
|
|
76
|
+
applying: true,
|
|
77
|
+
dryRun: false,
|
|
78
|
+
json
|
|
79
|
+
});
|
|
80
|
+
printManagedPreflight({
|
|
81
|
+
command: "upgrade",
|
|
82
|
+
...summarizeDiffPreflight(diffReport),
|
|
83
|
+
consentSource: consent.consentSource,
|
|
84
|
+
policyProfile: consent.policyProfile
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (yes && shouldPromptApplyConfirmation({ applying, dryRun, json, confirm, interactive })) {
|
|
89
|
+
const approved = await promptApplyConfirmation({ command: "upgrade", createPrompt });
|
|
90
|
+
if (!approved) {
|
|
91
|
+
return {
|
|
92
|
+
cancelled: true,
|
|
93
|
+
dryRun: true,
|
|
94
|
+
wrote: false,
|
|
95
|
+
installedVersion: cliVersion,
|
|
96
|
+
latestVersion,
|
|
97
|
+
latestCommand,
|
|
98
|
+
previewCommand,
|
|
99
|
+
result: null,
|
|
100
|
+
statePresent: Boolean(state)
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (state) {
|
|
106
|
+
result = await updateGlobalHarness({
|
|
107
|
+
packageRoot,
|
|
108
|
+
packageName,
|
|
109
|
+
cliVersion,
|
|
110
|
+
homeDir,
|
|
111
|
+
workspaceRoot,
|
|
112
|
+
dryRun: preview
|
|
113
|
+
});
|
|
114
|
+
} else if (preview) {
|
|
115
|
+
const outcome = await runHarnessSetup({
|
|
116
|
+
packageRoot,
|
|
117
|
+
packageName,
|
|
118
|
+
cliVersion,
|
|
119
|
+
homeDir,
|
|
120
|
+
workspaceRoot,
|
|
121
|
+
dryRun: true,
|
|
122
|
+
yes: false,
|
|
123
|
+
interactive: false
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
if (outcome.cancelled) {
|
|
127
|
+
throw new Error("Upgrade preview cancelled.");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
result = outcome.result;
|
|
131
|
+
} else {
|
|
132
|
+
throw new Error(`No managed state found. Run "${formatCliCommand("setup --yes")}" before upgrading.`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
dryRun: preview,
|
|
137
|
+
wrote: !preview,
|
|
138
|
+
installedVersion: cliVersion,
|
|
139
|
+
latestVersion,
|
|
140
|
+
latestCommand,
|
|
141
|
+
previewCommand,
|
|
142
|
+
result,
|
|
143
|
+
statePresent: Boolean(state)
|
|
144
|
+
};
|
|
145
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { constants } from "node:fs";
|
|
2
|
+
import { access, readFile, readdir } from "node:fs/promises";
|
|
3
|
+
import { relative, resolve, sep } from "node:path";
|
|
4
|
+
import { renderTemplate } from "./text-template.js";
|
|
5
|
+
|
|
6
|
+
export const MODES = new Set(["minimal", "standard", "enterprise"]);
|
|
7
|
+
export const ADAPTERS = new Set(["codex", "cursor", "claude", "gemini", "copilot", "opencode", "pi"]);
|
|
8
|
+
|
|
9
|
+
export const ADAPTER_PATHS = {
|
|
10
|
+
codex: [".codex/"],
|
|
11
|
+
cursor: [".cursor/"],
|
|
12
|
+
claude: [".claude/", "CLAUDE.md"],
|
|
13
|
+
gemini: ["GEMINI.md"],
|
|
14
|
+
copilot: [".github/copilot-instructions.md"],
|
|
15
|
+
opencode: [".opencode/", "opencode.json.sample"],
|
|
16
|
+
pi: [".pi/"]
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const TEXT_EXTENSIONS = new Set([".md", ".mdc", ".json", ".yml", ".yaml", ".sample", ".sh", ".mjs"]);
|
|
20
|
+
|
|
21
|
+
const MINIMAL_WHITELIST = new Set([
|
|
22
|
+
"AGENTS.md",
|
|
23
|
+
"CLAUDE.md",
|
|
24
|
+
"GEMINI.md",
|
|
25
|
+
"AGENT.md",
|
|
26
|
+
"docs/ai/architecture.md",
|
|
27
|
+
"docs/ai/harness.md",
|
|
28
|
+
"docs/ai/testing.md",
|
|
29
|
+
"docs/ai/git-workflow.md",
|
|
30
|
+
"docs/ai/memory.md",
|
|
31
|
+
"docs/ai/context-graph.md",
|
|
32
|
+
".cursor/rules/core.mdc",
|
|
33
|
+
".cursor/rules/testing.mdc",
|
|
34
|
+
".cursor/rules/git.mdc",
|
|
35
|
+
".github/copilot-instructions.md",
|
|
36
|
+
"setup-agent-links.sh"
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
const STANDARD_EXCLUDED_PREFIXES = [
|
|
40
|
+
"evals/",
|
|
41
|
+
".github/workflows/",
|
|
42
|
+
".opencode/loops/",
|
|
43
|
+
".gentle-ai/loops/",
|
|
44
|
+
"scripts/harness/"
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
export const COMPATIBILITY_LINKS = [
|
|
48
|
+
["CLAUDE.md", "AGENTS.md"],
|
|
49
|
+
["GEMINI.md", "AGENTS.md"],
|
|
50
|
+
[".claude/CLAUDE.md", "../AGENTS.md"],
|
|
51
|
+
[".agent/AGENTS.md", "../AGENTS.md"],
|
|
52
|
+
[".windsurfrules", "AGENTS.md"]
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
export async function listTemplateFiles(packageRoot, mode, options = {}) {
|
|
56
|
+
if (!MODES.has(mode)) {
|
|
57
|
+
throw new Error(`Invalid mode "${mode}". Use minimal, standard, or enterprise.`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const templateRoot = resolve(packageRoot, "repo-template");
|
|
61
|
+
const absolutePaths = await listFilesRecursive(templateRoot);
|
|
62
|
+
const selectedAdapters = normalizeAdapters(options.adapters);
|
|
63
|
+
|
|
64
|
+
return absolutePaths
|
|
65
|
+
.map((sourcePath) => ({ sourcePath, relativePath: normalizePath(relative(templateRoot, sourcePath)) }))
|
|
66
|
+
.filter(({ relativePath }) => shouldInstall(relativePath, mode, selectedAdapters));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function listFilesRecursive(root) {
|
|
70
|
+
const entries = await readdir(root, { withFileTypes: true });
|
|
71
|
+
const files = [];
|
|
72
|
+
|
|
73
|
+
for (const entry of entries) {
|
|
74
|
+
const fullPath = resolve(root, entry.name);
|
|
75
|
+
if (entry.isDirectory()) files.push(...await listFilesRecursive(fullPath));
|
|
76
|
+
else if (entry.isFile()) files.push(fullPath);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return files;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function shouldInstall(relativePath, mode, selectedAdapters) {
|
|
83
|
+
const includedByMode = mode === "enterprise"
|
|
84
|
+
? true
|
|
85
|
+
: mode === "minimal"
|
|
86
|
+
? MINIMAL_WHITELIST.has(relativePath)
|
|
87
|
+
: !STANDARD_EXCLUDED_PREFIXES.some((prefix) => relativePath.startsWith(prefix));
|
|
88
|
+
|
|
89
|
+
if (!includedByMode) return false;
|
|
90
|
+
if (!selectedAdapters) return true;
|
|
91
|
+
|
|
92
|
+
const adapter = adapterForPath(relativePath);
|
|
93
|
+
if (!adapter) return true;
|
|
94
|
+
|
|
95
|
+
return selectedAdapters.has(adapter);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function normalizeAdapters(adapters) {
|
|
99
|
+
if (adapters == null) return null;
|
|
100
|
+
|
|
101
|
+
const normalized = new Set();
|
|
102
|
+
for (const adapter of adapters) {
|
|
103
|
+
if (!ADAPTERS.has(adapter)) {
|
|
104
|
+
throw new Error(`Invalid adapter "${adapter}". Use ${[...ADAPTERS].join(", ")}.`);
|
|
105
|
+
}
|
|
106
|
+
normalized.add(adapter);
|
|
107
|
+
}
|
|
108
|
+
return normalized;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function adapterForPath(relativePath) {
|
|
112
|
+
for (const [adapter, patterns] of Object.entries(ADAPTER_PATHS)) {
|
|
113
|
+
if (patterns.some((pattern) => relativePath === pattern || relativePath.startsWith(pattern))) {
|
|
114
|
+
return adapter;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function normalizePath(filePath) {
|
|
122
|
+
return sep === "/" ? filePath : filePath.split(sep).join("/");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function isTextFile(filePath) {
|
|
126
|
+
return TEXT_EXTENSIONS.has(filePath.slice(filePath.lastIndexOf(".")));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export async function pathExists(filePath) {
|
|
130
|
+
try {
|
|
131
|
+
await access(filePath, constants.F_OK);
|
|
132
|
+
return true;
|
|
133
|
+
} catch {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export async function renderFileContent(sourcePath, project) {
|
|
139
|
+
if (isTextFile(sourcePath)) {
|
|
140
|
+
const text = await readFile(sourcePath, "utf8");
|
|
141
|
+
return Buffer.from(renderTemplate(text, project), "utf8");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return readFile(sourcePath);
|
|
145
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { mkdir, readFile, symlink, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { COMPATIBILITY_LINKS, MODES, adapterForPath, listTemplateFiles, pathExists, renderFileContent } from "./harness-files.js";
|
|
4
|
+
import { hashBuffer } from "./hash.js";
|
|
5
|
+
import { createManifest, readManifest, writeManifest } from "./manifest.js";
|
|
6
|
+
import { formatCliCommand } from "./global/brand/cli.js";
|
|
7
|
+
|
|
8
|
+
export async function updateHarness({ project, packageRoot, packageName, cliVersion, mode, adapters, force = false, dryRun = false }) {
|
|
9
|
+
const manifest = await readManifest(project.root);
|
|
10
|
+
|
|
11
|
+
if (!manifest) {
|
|
12
|
+
throw new Error(`No .harness/manifest.json found. Run "${formatCliCommand("init")}" first.`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const effectiveMode = mode ?? manifest.mode;
|
|
16
|
+
|
|
17
|
+
if (!MODES.has(effectiveMode)) {
|
|
18
|
+
throw new Error(`Invalid mode "${effectiveMode}". Use minimal, standard, or enterprise.`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const effectiveAdapters = adapters == null ? manifest.adapters ?? null : [...adapters].sort();
|
|
22
|
+
const templateFiles = await listTemplateFiles(packageRoot, effectiveMode, { adapters: effectiveAdapters });
|
|
23
|
+
const result = {
|
|
24
|
+
mode: effectiveMode,
|
|
25
|
+
adapters: effectiveAdapters,
|
|
26
|
+
created: [],
|
|
27
|
+
updated: [],
|
|
28
|
+
unchanged: [],
|
|
29
|
+
skippedModified: [],
|
|
30
|
+
skippedUntracked: []
|
|
31
|
+
};
|
|
32
|
+
const nextFiles = { ...manifest.files };
|
|
33
|
+
|
|
34
|
+
for (const { relativePath, sourcePath } of templateFiles) {
|
|
35
|
+
await reconcileFile({ project, relativePath, sourcePath, manifest, force, dryRun, result, nextFiles });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
await createMissingCompatibilityLinks(project.root, { adapters: effectiveAdapters, dryRun, result });
|
|
39
|
+
|
|
40
|
+
if (!dryRun) {
|
|
41
|
+
await writeManifest(project.root, createManifest({
|
|
42
|
+
packageName: manifest.packageName ?? packageName,
|
|
43
|
+
cliVersion,
|
|
44
|
+
mode: effectiveMode,
|
|
45
|
+
adapters: effectiveAdapters,
|
|
46
|
+
files: nextFiles,
|
|
47
|
+
installedAt: manifest.installedAt
|
|
48
|
+
}));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function reconcileFile({ project, relativePath, sourcePath, manifest, force, dryRun, result, nextFiles }) {
|
|
55
|
+
const destinationPath = resolve(project.root, relativePath);
|
|
56
|
+
const newContent = await renderFileContent(sourcePath, project);
|
|
57
|
+
const newHash = hashBuffer(newContent);
|
|
58
|
+
const exists = await pathExists(destinationPath);
|
|
59
|
+
|
|
60
|
+
if (!exists) {
|
|
61
|
+
if (!dryRun) await writeManagedFile(destinationPath, newContent);
|
|
62
|
+
result.created.push(relativePath);
|
|
63
|
+
nextFiles[relativePath] = newHash;
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const trackedHash = manifest.files[relativePath];
|
|
68
|
+
const currentHash = hashBuffer(await readFile(destinationPath));
|
|
69
|
+
const isUnmodifiedSinceInstall = Boolean(trackedHash) && trackedHash === currentHash;
|
|
70
|
+
|
|
71
|
+
if (isUnmodifiedSinceInstall) {
|
|
72
|
+
nextFiles[relativePath] = newHash;
|
|
73
|
+
|
|
74
|
+
if (currentHash === newHash) {
|
|
75
|
+
result.unchanged.push(relativePath);
|
|
76
|
+
} else {
|
|
77
|
+
if (!dryRun) await writeManagedFile(destinationPath, newContent);
|
|
78
|
+
result.updated.push(relativePath);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!force) {
|
|
85
|
+
result[trackedHash ? "skippedModified" : "skippedUntracked"].push(relativePath);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!dryRun) await writeManagedFile(destinationPath, newContent);
|
|
90
|
+
result.updated.push(relativePath);
|
|
91
|
+
nextFiles[relativePath] = newHash;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function writeManagedFile(destinationPath, content) {
|
|
95
|
+
await mkdir(dirname(destinationPath), { recursive: true });
|
|
96
|
+
await writeFile(destinationPath, content);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function createMissingCompatibilityLinks(root, { adapters, dryRun, result }) {
|
|
100
|
+
for (const [linkPath, target] of COMPATIBILITY_LINKS) {
|
|
101
|
+
const adapter = adapterForPath(linkPath);
|
|
102
|
+
if (adapters && adapter && !adapters.includes(adapter)) continue;
|
|
103
|
+
|
|
104
|
+
const destination = resolve(root, linkPath);
|
|
105
|
+
const exists = await pathExists(destination);
|
|
106
|
+
|
|
107
|
+
if (exists) continue;
|
|
108
|
+
|
|
109
|
+
if (!dryRun) {
|
|
110
|
+
await mkdir(dirname(destination), { recursive: true });
|
|
111
|
+
try {
|
|
112
|
+
await symlink(target, destination);
|
|
113
|
+
} catch {
|
|
114
|
+
await writeFile(destination, `Compatibility pointer. Source of truth: ${target}\n`, "utf8");
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
result.created.push(linkPath);
|
|
119
|
+
}
|
|
120
|
+
}
|
package/src/hash.js
ADDED
package/src/manifest.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, resolve } from "node:path";
|
|
4
|
+
|
|
5
|
+
export const MANIFEST_RELATIVE_PATH = ".harness/manifest.json";
|
|
6
|
+
|
|
7
|
+
export async function readManifest(projectRoot) {
|
|
8
|
+
const manifestPath = resolve(projectRoot, MANIFEST_RELATIVE_PATH);
|
|
9
|
+
if (!existsSync(manifestPath)) return null;
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
return JSON.parse(await readFile(manifestPath, "utf8"));
|
|
13
|
+
} catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function writeManifest(projectRoot, manifest) {
|
|
19
|
+
const manifestPath = resolve(projectRoot, MANIFEST_RELATIVE_PATH);
|
|
20
|
+
await mkdir(dirname(manifestPath), { recursive: true });
|
|
21
|
+
await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function createManifest({ packageName, cliVersion, mode, adapters, files, installedAt }) {
|
|
25
|
+
const now = new Date().toISOString();
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
packageName,
|
|
29
|
+
cliVersion,
|
|
30
|
+
mode,
|
|
31
|
+
adapters,
|
|
32
|
+
installedAt: installedAt ?? now,
|
|
33
|
+
updatedAt: now,
|
|
34
|
+
files
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { basename, resolve } from "node:path";
|
|
4
|
+
|
|
5
|
+
export async function detectProject(cwd) {
|
|
6
|
+
const root = resolve(cwd);
|
|
7
|
+
const packageJsonPath = resolve(root, "package.json");
|
|
8
|
+
const packageJson = existsSync(packageJsonPath)
|
|
9
|
+
? JSON.parse(await readFile(packageJsonPath, "utf8"))
|
|
10
|
+
: null;
|
|
11
|
+
|
|
12
|
+
const packageManager = detectPackageManager(root, packageJson);
|
|
13
|
+
const scripts = packageJson?.scripts ?? {};
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
root,
|
|
17
|
+
name: packageJson?.name ?? basename(root),
|
|
18
|
+
purpose: packageJson?.description ?? "AI-assisted software project",
|
|
19
|
+
packageManager,
|
|
20
|
+
stack: detectStack(root, packageJson),
|
|
21
|
+
architecturePattern: detectArchitecture(root),
|
|
22
|
+
detectedAdapters: detectAdapters(root),
|
|
23
|
+
commands: {
|
|
24
|
+
install: installCommand(packageManager),
|
|
25
|
+
dev: scriptCommand(packageManager, scripts, "dev"),
|
|
26
|
+
lint: scriptCommand(packageManager, scripts, "lint"),
|
|
27
|
+
format: scriptCommand(packageManager, scripts, "format"),
|
|
28
|
+
typeCheck: scriptCommand(packageManager, scripts, "typecheck") ?? scriptCommand(packageManager, scripts, "type-check"),
|
|
29
|
+
test: scriptCommand(packageManager, scripts, "test"),
|
|
30
|
+
build: scriptCommand(packageManager, scripts, "build")
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function detectPackageManager(root, packageJson) {
|
|
36
|
+
if (existsSync(resolve(root, "pnpm-lock.yaml"))) return "pnpm";
|
|
37
|
+
if (existsSync(resolve(root, "yarn.lock"))) return "yarn";
|
|
38
|
+
if (existsSync(resolve(root, "bun.lockb"))) return "bun";
|
|
39
|
+
if (existsSync(resolve(root, "package-lock.json"))) return "npm";
|
|
40
|
+
|
|
41
|
+
const declared = packageJson?.packageManager?.split("@")[0];
|
|
42
|
+
return declared ?? "npm";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function detectStack(root, packageJson) {
|
|
46
|
+
const dependencies = {
|
|
47
|
+
...packageJson?.dependencies,
|
|
48
|
+
...packageJson?.devDependencies
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
if (dependencies.next || existsSync(resolve(root, "next.config.js")) || existsSync(resolve(root, "next.config.mjs"))) return "Next.js";
|
|
52
|
+
if (dependencies.vite || existsSync(resolve(root, "vite.config.js")) || existsSync(resolve(root, "vite.config.ts"))) return "Vite";
|
|
53
|
+
if (dependencies.react) return "React";
|
|
54
|
+
if (existsSync(resolve(root, "pyproject.toml"))) return "Python";
|
|
55
|
+
if (existsSync(resolve(root, "go.mod"))) return "Go";
|
|
56
|
+
if (existsSync(resolve(root, "Cargo.toml"))) return "Rust";
|
|
57
|
+
if (packageJson) return "Node.js";
|
|
58
|
+
return "Unknown";
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function detectArchitecture(root) {
|
|
62
|
+
if (existsSync(resolve(root, "src/domain")) || existsSync(resolve(root, "src/application"))) {
|
|
63
|
+
return "Layered or Clean Architecture";
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (existsSync(resolve(root, "app")) || existsSync(resolve(root, "pages"))) {
|
|
67
|
+
return "Framework-driven application";
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return "To be documented";
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function detectAdapters(root) {
|
|
74
|
+
const markers = [
|
|
75
|
+
["codex", ".codex"],
|
|
76
|
+
["cursor", ".cursor"],
|
|
77
|
+
["claude", ".claude"],
|
|
78
|
+
["claude", "CLAUDE.md"],
|
|
79
|
+
["gemini", "GEMINI.md"],
|
|
80
|
+
["copilot", ".github/copilot-instructions.md"],
|
|
81
|
+
["opencode", ".opencode"],
|
|
82
|
+
["opencode", "opencode.json"],
|
|
83
|
+
["opencode", "opencode.json.sample"],
|
|
84
|
+
["pi", ".pi"]
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
return [...new Set(
|
|
88
|
+
markers
|
|
89
|
+
.filter(([, markerPath]) => existsSync(resolve(root, markerPath)))
|
|
90
|
+
.map(([adapter]) => adapter)
|
|
91
|
+
)].sort();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function installCommand(packageManager) {
|
|
95
|
+
if (packageManager === "pnpm") return "pnpm install";
|
|
96
|
+
if (packageManager === "yarn") return "yarn install";
|
|
97
|
+
if (packageManager === "bun") return "bun install";
|
|
98
|
+
return "npm install";
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function scriptCommand(packageManager, scripts, scriptName) {
|
|
102
|
+
if (!scripts[scriptName]) return "Not configured";
|
|
103
|
+
if (packageManager === "npm") return `npm run ${scriptName}`;
|
|
104
|
+
return `${packageManager} ${scriptName}`;
|
|
105
|
+
}
|