@entelligentsia/forgecli 1.0.21 → 1.0.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +346 -0
- package/README.md +2 -0
- package/dist/CHANGELOG-forge-plugin.md +281 -0
- package/dist/bin/argv.d.ts +2 -2
- package/dist/bin/argv.js +25 -0
- package/dist/bin/argv.js.map +1 -1
- package/dist/bin/forge.js +12 -0
- package/dist/bin/forge.js.map +1 -1
- package/dist/bin/init.d.ts +23 -0
- package/dist/bin/init.js +123 -0
- package/dist/bin/init.js.map +1 -0
- package/dist/bin/uninstall.d.ts +20 -0
- package/dist/bin/uninstall.js +141 -0
- package/dist/bin/uninstall.js.map +1 -0
- package/dist/extensions/forgecli/claude-bootstrap/bootstrap.d.ts +40 -0
- package/dist/extensions/forgecli/claude-bootstrap/bootstrap.js +593 -0
- package/dist/extensions/forgecli/claude-bootstrap/bootstrap.js.map +1 -0
- package/dist/extensions/forgecli/claude-bootstrap/settings-merge.d.ts +46 -0
- package/dist/extensions/forgecli/claude-bootstrap/settings-merge.js +245 -0
- package/dist/extensions/forgecli/claude-bootstrap/settings-merge.js.map +1 -0
- package/dist/extensions/forgecli/claude-bootstrap/uninstall.d.ts +23 -0
- package/dist/extensions/forgecli/claude-bootstrap/uninstall.js +215 -0
- package/dist/extensions/forgecli/claude-bootstrap/uninstall.js.map +1 -0
- package/dist/extensions/forgecli/dashboard/component.js +10 -7
- package/dist/extensions/forgecli/dashboard/component.js.map +1 -1
- package/dist/extensions/forgecli/forge-tools.d.ts +1 -0
- package/dist/extensions/forgecli/forge-tools.js +73 -0
- package/dist/extensions/forgecli/forge-tools.js.map +1 -1
- package/dist/extensions/forgecli/lib/forge-root.d.ts +5 -0
- package/dist/extensions/forgecli/lib/forge-root.js +14 -1
- package/dist/extensions/forgecli/lib/forge-root.js.map +1 -1
- package/dist/extensions/forgecli/orchestrators/bug/bug-body.d.ts +1 -0
- package/dist/extensions/forgecli/orchestrators/bug/bug-body.js +65 -0
- package/dist/extensions/forgecli/orchestrators/bug/bug-body.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/bug/bug-id.d.ts +23 -0
- package/dist/extensions/forgecli/orchestrators/bug/bug-id.js +140 -0
- package/dist/extensions/forgecli/orchestrators/bug/bug-id.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/bug/bug-phase-dispatch.d.ts +54 -0
- package/dist/extensions/forgecli/orchestrators/bug/bug-phase-dispatch.js +349 -0
- package/dist/extensions/forgecli/orchestrators/bug/bug-phase-dispatch.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/bug/bug-phases.d.ts +8 -0
- package/dist/extensions/forgecli/orchestrators/bug/bug-phases.js +60 -0
- package/dist/extensions/forgecli/orchestrators/bug/bug-phases.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/bug/bug-state.d.ts +14 -0
- package/dist/extensions/forgecli/orchestrators/bug/bug-state.js +100 -0
- package/dist/extensions/forgecli/orchestrators/bug/bug-state.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/bug/bug-triage-routing.d.ts +72 -0
- package/dist/extensions/forgecli/orchestrators/bug/bug-triage-routing.js +204 -0
- package/dist/extensions/forgecli/orchestrators/bug/bug-triage-routing.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/bug/bug-verdict-loop.d.ts +38 -0
- package/dist/extensions/forgecli/orchestrators/bug/bug-verdict-loop.js +166 -0
- package/dist/extensions/forgecli/orchestrators/bug/bug-verdict-loop.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/bug/bug-verdict.d.ts +3 -0
- package/dist/extensions/forgecli/orchestrators/bug/bug-verdict.js +55 -0
- package/dist/extensions/forgecli/orchestrators/bug/bug-verdict.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/bug/run-bug-command.d.ts +7 -0
- package/dist/extensions/forgecli/orchestrators/bug/run-bug-command.js +293 -0
- package/dist/extensions/forgecli/orchestrators/bug/run-bug-command.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/bug/run-bug-pipeline.d.ts +2 -0
- package/dist/extensions/forgecli/orchestrators/bug/run-bug-pipeline.js +501 -0
- package/dist/extensions/forgecli/orchestrators/bug/run-bug-pipeline.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/bug/run-bug-types.d.ts +41 -0
- package/dist/extensions/forgecli/orchestrators/bug/run-bug-types.js +5 -0
- package/dist/extensions/forgecli/orchestrators/bug/run-bug-types.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/common/orchestrator-entry.d.ts +43 -0
- package/dist/extensions/forgecli/orchestrators/common/orchestrator-entry.js +85 -0
- package/dist/extensions/forgecli/orchestrators/common/orchestrator-entry.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/common/orchestrator-misc.d.ts +8 -0
- package/dist/extensions/forgecli/orchestrators/common/orchestrator-misc.js +37 -0
- package/dist/extensions/forgecli/orchestrators/common/orchestrator-misc.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/common/orchestrator-notify.d.ts +28 -0
- package/dist/extensions/forgecli/orchestrators/common/orchestrator-notify.js +45 -0
- package/dist/extensions/forgecli/orchestrators/common/orchestrator-notify.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/common/orchestrator-transcript-session.d.ts +26 -0
- package/dist/extensions/forgecli/orchestrators/common/orchestrator-transcript-session.js +75 -0
- package/dist/extensions/forgecli/orchestrators/common/orchestrator-transcript-session.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/common/summary-recovery.d.ts +24 -0
- package/dist/extensions/forgecli/orchestrators/common/summary-recovery.js +37 -0
- package/dist/extensions/forgecli/orchestrators/common/summary-recovery.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/fix-bug.d.ts +9 -92
- package/dist/extensions/forgecli/orchestrators/fix-bug.js +23 -1695
- package/dist/extensions/forgecli/orchestrators/fix-bug.js.map +1 -1
- package/dist/extensions/forgecli/orchestrators/run-sprint.d.ts +3 -12
- package/dist/extensions/forgecli/orchestrators/run-sprint.js +97 -270
- package/dist/extensions/forgecli/orchestrators/run-sprint.js.map +1 -1
- package/dist/extensions/forgecli/orchestrators/run-task.d.ts +10 -214
- package/dist/extensions/forgecli/orchestrators/run-task.js +31 -1481
- package/dist/extensions/forgecli/orchestrators/run-task.js.map +1 -1
- package/dist/extensions/forgecli/orchestrators/sprint/sprint-ceremony.d.ts +33 -0
- package/dist/extensions/forgecli/orchestrators/sprint/sprint-ceremony.js +135 -0
- package/dist/extensions/forgecli/orchestrators/sprint/sprint-ceremony.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/sprint/sprint-state.d.ts +18 -0
- package/dist/extensions/forgecli/orchestrators/sprint/sprint-state.js +55 -0
- package/dist/extensions/forgecli/orchestrators/sprint/sprint-state.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/task/run-task-command.d.ts +9 -0
- package/dist/extensions/forgecli/orchestrators/task/run-task-command.js +174 -0
- package/dist/extensions/forgecli/orchestrators/task/run-task-command.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/task/run-task-pipeline.d.ts +2 -0
- package/dist/extensions/forgecli/orchestrators/task/run-task-pipeline.js +494 -0
- package/dist/extensions/forgecli/orchestrators/task/run-task-pipeline.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/task/run-task-types.d.ts +62 -0
- package/dist/extensions/forgecli/orchestrators/task/run-task-types.js +5 -0
- package/dist/extensions/forgecli/orchestrators/task/run-task-types.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/task/task-body.d.ts +4 -0
- package/dist/extensions/forgecli/orchestrators/task/task-body.js +48 -0
- package/dist/extensions/forgecli/orchestrators/task/task-body.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/task/task-events.d.ts +63 -0
- package/dist/extensions/forgecli/orchestrators/task/task-events.js +185 -0
- package/dist/extensions/forgecli/orchestrators/task/task-events.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/task/task-gates.d.ts +34 -0
- package/dist/extensions/forgecli/orchestrators/task/task-gates.js +78 -0
- package/dist/extensions/forgecli/orchestrators/task/task-gates.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/task/task-phase-dispatch.d.ts +42 -0
- package/dist/extensions/forgecli/orchestrators/task/task-phase-dispatch.js +370 -0
- package/dist/extensions/forgecli/orchestrators/task/task-phase-dispatch.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/task/task-phases.d.ts +14 -0
- package/dist/extensions/forgecli/orchestrators/task/task-phases.js +26 -0
- package/dist/extensions/forgecli/orchestrators/task/task-phases.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/task/task-record.d.ts +9 -0
- package/dist/extensions/forgecli/orchestrators/task/task-record.js +58 -0
- package/dist/extensions/forgecli/orchestrators/task/task-record.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/task/task-state.d.ts +14 -0
- package/dist/extensions/forgecli/orchestrators/task/task-state.js +35 -0
- package/dist/extensions/forgecli/orchestrators/task/task-state.js.map +1 -0
- package/dist/extensions/forgecli/orchestrators/task/task-verdict-loop.d.ts +36 -0
- package/dist/extensions/forgecli/orchestrators/task/task-verdict-loop.js +152 -0
- package/dist/extensions/forgecli/orchestrators/task/task-verdict-loop.js.map +1 -0
- package/dist/extensions/forgecli/update/forge-update-command.js +10 -7
- package/dist/extensions/forgecli/update/forge-update-command.js.map +1 -1
- package/dist/forge-payload/.base-pack/commands/approve.md +2 -2
- package/dist/forge-payload/.base-pack/commands/check-agent.md +2 -2
- package/dist/forge-payload/.base-pack/commands/collate.md +2 -2
- package/dist/forge-payload/.base-pack/commands/commit.md +2 -2
- package/dist/forge-payload/.base-pack/commands/enhance.md +2 -2
- package/dist/forge-payload/.base-pack/commands/fix-bug.md +2 -2
- package/dist/forge-payload/.base-pack/commands/implement.md +2 -2
- package/dist/forge-payload/.base-pack/commands/init.md +278 -0
- package/dist/forge-payload/.base-pack/commands/new-sprint.md +2 -2
- package/dist/forge-payload/.base-pack/commands/plan-sprint.md +2 -2
- package/dist/forge-payload/.base-pack/commands/plan.md +2 -2
- package/dist/forge-payload/.base-pack/commands/retro.md +2 -2
- package/dist/forge-payload/.base-pack/commands/review-code.md +2 -2
- package/dist/forge-payload/.base-pack/commands/review-plan.md +2 -2
- package/dist/forge-payload/.base-pack/commands/run-sprint.md +2 -2
- package/dist/forge-payload/.base-pack/commands/run-task.md +2 -2
- package/dist/forge-payload/.base-pack/commands/validate.md +2 -2
- package/dist/forge-payload/.base-pack/workflows/_fragments/event-emission-schema.md +4 -0
- package/dist/forge-payload/.base-pack/workflows/_fragments/event-vocabulary.md +88 -0
- package/dist/forge-payload/.base-pack/workflows/collator_agent.md +5 -6
- package/dist/forge-payload/.base-pack/workflows/commit_task.md +41 -38
- package/dist/forge-payload/.base-pack/workflows/implement_plan.md +3 -3
- package/dist/forge-payload/.base-pack/workflows/migrate_structural.md +1 -1
- package/dist/forge-payload/.base-pack/workflows-js/wfl-fix-bug.js +42 -6
- package/dist/forge-payload/.base-pack/workflows-js/wfl-init.js +449 -0
- package/dist/forge-payload/.base-pack/workflows-js/wfl-run-task.js +32 -1
- package/dist/forge-payload/.claude-plugin/plugin.json +1 -1
- package/dist/forge-payload/.schemas/enum-catalog.json +2 -2
- package/dist/forge-payload/.schemas/event.schema.json +8 -3
- package/dist/forge-payload/.schemas/migrations.json +141 -0
- package/dist/forge-payload/commands/add-pipeline.md +1 -1
- package/dist/forge-payload/commands/add-task.md +3 -3
- package/dist/forge-payload/commands/ask.md +1 -1
- package/dist/forge-payload/commands/check-agent.md +1 -1
- package/dist/forge-payload/commands/config.md +1 -1
- package/dist/forge-payload/commands/health.md +1 -1
- package/dist/forge-payload/commands/init.md +62 -7
- package/dist/forge-payload/commands/rebuild.md +3 -3
- package/dist/forge-payload/commands/remove.md +1 -1
- package/dist/forge-payload/commands/repair.md +1 -1
- package/dist/forge-payload/commands/report-bug.md +1 -1
- package/dist/forge-payload/commands/status.md +1 -1
- package/dist/forge-payload/commands/update.md +3 -3
- package/dist/forge-payload/hooks/lib/common.cjs +228 -0
- package/dist/forge-payload/hooks/lib/plugin-detection.cjs +106 -0
- package/dist/forge-payload/hooks/lib/update-msg.cjs +23 -0
- package/dist/forge-payload/hooks/lib/update-url.cjs +46 -0
- package/dist/forge-payload/hooks/lib/write-registry.js +53 -0
- package/dist/forge-payload/init/discovery/discover-database.md +32 -0
- package/dist/forge-payload/init/discovery/discover-processes.md +31 -0
- package/dist/forge-payload/init/discovery/discover-routing.md +31 -0
- package/dist/forge-payload/init/discovery/discover-stack.md +33 -0
- package/dist/forge-payload/init/discovery/discover-testing.md +34 -0
- package/dist/forge-payload/init/generation/generate-commands.md +171 -0
- package/dist/forge-payload/init/generation/generate-kb-doc.md +60 -0
- package/dist/forge-payload/init/generation/generate-knowledge-base.md +56 -0
- package/dist/forge-payload/init/generation/generate-persona.md +73 -0
- package/dist/forge-payload/init/generation/generate-personas.md +54 -0
- package/dist/forge-payload/init/generation/generate-skill.md +66 -0
- package/dist/forge-payload/init/generation/generate-skills.md +36 -0
- package/dist/forge-payload/init/generation/generate-template.md +60 -0
- package/dist/forge-payload/init/generation/generate-templates.md +39 -0
- package/dist/forge-payload/init/generation/generate-tools.md +133 -0
- package/dist/forge-payload/init/generation/generate-workflows.md +78 -0
- package/dist/forge-payload/init/phases/phase-1-collect.md +10 -2
- package/dist/forge-payload/init/phases/phase-3-materialize.md +1 -1
- package/dist/forge-payload/init/phases/phase-4-register.md +8 -0
- package/dist/forge-payload/init/workflow-gen-plan.json +17 -0
- package/dist/forge-payload/integrity.json +16 -16
- package/dist/forge-payload/meta/store-schema/event.schema.md +7 -0
- package/dist/forge-payload/meta/workflows/_fragments/event-emission-schema.md +4 -0
- package/dist/forge-payload/meta/workflows/_fragments/event-vocabulary.md +88 -0
- package/dist/forge-payload/meta/workflows/meta-collate.md +5 -6
- package/dist/forge-payload/meta/workflows/meta-commit.md +46 -43
- package/dist/forge-payload/meta/workflows/meta-fix-bug.md +7 -2
- package/dist/forge-payload/meta/workflows/meta-implement.md +3 -3
- package/dist/forge-payload/meta/workflows/meta-migrate.md +1 -1
- package/dist/forge-payload/meta/workflows/meta-orchestrate.md +4 -1
- package/dist/forge-payload/schemas/enum-catalog.json +2 -2
- package/dist/forge-payload/schemas/event.schema.json +8 -3
- package/dist/forge-payload/schemas/structure-manifest.json +5 -12
- package/dist/forge-payload/tools/commit-task.cjs +218 -0
- package/dist/forge-payload/tools/forge-preflight.cjs +268 -0
- package/dist/forge-payload/tools/lib/paths.cjs +12 -11
- package/dist/forge-payload/tools/lib/pricing.cjs +31 -11
- package/dist/forge-payload/tools/query-logger.cjs +34 -0
- package/dist/forge-payload/tools/store-cli.cjs +6 -1
- package/dist/forge-payload/tools/substitute-placeholders.cjs +5 -6
- package/package.json +2 -2
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export interface MergeResult {
|
|
2
|
+
outcome: "created" | "merged" | "already-present" | "error";
|
|
3
|
+
warning?: string;
|
|
4
|
+
}
|
|
5
|
+
interface HookEntry {
|
|
6
|
+
type: "command";
|
|
7
|
+
command: string;
|
|
8
|
+
timeout: number;
|
|
9
|
+
}
|
|
10
|
+
interface SessionStartGroup {
|
|
11
|
+
hooks: HookEntry[];
|
|
12
|
+
}
|
|
13
|
+
interface MatcherGroup {
|
|
14
|
+
matcher: string;
|
|
15
|
+
hooks: HookEntry[];
|
|
16
|
+
}
|
|
17
|
+
export interface ForgeSettingsHooks {
|
|
18
|
+
SessionStart: SessionStartGroup[];
|
|
19
|
+
PreToolUse: MatcherGroup[];
|
|
20
|
+
PostToolUse: MatcherGroup[];
|
|
21
|
+
PermissionRequest: MatcherGroup[];
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Returns the canonical Forge hooks block for project .claude/settings.json.
|
|
25
|
+
*
|
|
26
|
+
* All commands point at $CLAUDE_PROJECT_DIR/.forge/tools/hooks/<name>.cjs
|
|
27
|
+
* (CLAUDE_PROJECT_DIR is a Claude Code runtime env var; not resolved at build time).
|
|
28
|
+
*
|
|
29
|
+
* Hook list mirrors forge/forge/hooks/hooks.json, with commands re-targeted
|
|
30
|
+
* from ${CLAUDE_PLUGIN_ROOT}/hooks/ to $CLAUDE_PROJECT_DIR/.forge/tools/hooks/.
|
|
31
|
+
*/
|
|
32
|
+
export declare function buildForgeHooksBlock(): ForgeSettingsHooks;
|
|
33
|
+
/**
|
|
34
|
+
* Merge Forge hooks into the project .claude/settings.json with merge-not-clobber semantics.
|
|
35
|
+
*
|
|
36
|
+
* Rules:
|
|
37
|
+
* 1. File absent → create with { "hooks": <forgeHooksBlock> }.
|
|
38
|
+
* 2. File present, valid JSON, no hooks key → add hooks key, preserve all other keys.
|
|
39
|
+
* 3. File present, valid JSON, hooks key with all Forge entries already present → "already-present" (no-op).
|
|
40
|
+
* 4. File present, valid JSON, hooks key with unrelated or missing Forge entries →
|
|
41
|
+
* merge Forge entries, preserve all existing non-Forge entries.
|
|
42
|
+
* 5. File present, malformed JSON → "error", file never overwritten.
|
|
43
|
+
* 6. Write failure → "error" with descriptive warning.
|
|
44
|
+
*/
|
|
45
|
+
export declare function mergeForgeHooks(settingsPath: string): MergeResult;
|
|
46
|
+
export {};
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
// claude-bootstrap/settings-merge.ts — Forge hooks merge into .claude/settings.json (FORGE-S31-T03)
|
|
2
|
+
//
|
|
3
|
+
// Provides merge-not-clobber semantics for writing Forge's hook configuration into
|
|
4
|
+
// a project's .claude/settings.json. Existing keys (including hooks from other tools)
|
|
5
|
+
// are preserved byte-identical. Forge entries are only added where missing.
|
|
6
|
+
//
|
|
7
|
+
// Key contracts:
|
|
8
|
+
// - Never overwrites a file that does not parse as JSON
|
|
9
|
+
// - Running twice is idempotent: second run detects existing Forge entries and returns "already-present"
|
|
10
|
+
// - Pure fs operations: no shell-outs, no LLM, no network
|
|
11
|
+
import * as fs from "node:fs";
|
|
12
|
+
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
13
|
+
/** Sentinel string used to identify Forge hook commands for dedup detection. */
|
|
14
|
+
const FORGE_HOOK_SENTINEL = ".forge/tools/hooks/";
|
|
15
|
+
// ── buildForgeHooksBlock ──────────────────────────────────────────────────────
|
|
16
|
+
/**
|
|
17
|
+
* Returns the canonical Forge hooks block for project .claude/settings.json.
|
|
18
|
+
*
|
|
19
|
+
* All commands point at $CLAUDE_PROJECT_DIR/.forge/tools/hooks/<name>.cjs
|
|
20
|
+
* (CLAUDE_PROJECT_DIR is a Claude Code runtime env var; not resolved at build time).
|
|
21
|
+
*
|
|
22
|
+
* Hook list mirrors forge/forge/hooks/hooks.json, with commands re-targeted
|
|
23
|
+
* from ${CLAUDE_PLUGIN_ROOT}/hooks/ to $CLAUDE_PROJECT_DIR/.forge/tools/hooks/.
|
|
24
|
+
*/
|
|
25
|
+
export function buildForgeHooksBlock() {
|
|
26
|
+
return {
|
|
27
|
+
SessionStart: [
|
|
28
|
+
{
|
|
29
|
+
hooks: [
|
|
30
|
+
{
|
|
31
|
+
type: "command",
|
|
32
|
+
command: 'node "$CLAUDE_PROJECT_DIR/.forge/tools/hooks/check-update.cjs"',
|
|
33
|
+
timeout: 10000,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
type: "command",
|
|
37
|
+
command: 'node "$CLAUDE_PROJECT_DIR/.forge/tools/hooks/preflight-session.cjs"',
|
|
38
|
+
timeout: 10000,
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
PreToolUse: [
|
|
44
|
+
{
|
|
45
|
+
matcher: "Write|Edit|MultiEdit",
|
|
46
|
+
hooks: [
|
|
47
|
+
{
|
|
48
|
+
type: "command",
|
|
49
|
+
command: 'node "$CLAUDE_PROJECT_DIR/.forge/tools/hooks/validate-write.cjs"',
|
|
50
|
+
timeout: 5000,
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
PostToolUse: [
|
|
56
|
+
{
|
|
57
|
+
matcher: "Bash",
|
|
58
|
+
hooks: [
|
|
59
|
+
{
|
|
60
|
+
type: "command",
|
|
61
|
+
command: 'node "$CLAUDE_PROJECT_DIR/.forge/tools/hooks/triage-error.cjs"',
|
|
62
|
+
timeout: 5000,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
// query-logger is a tool, not a hook script — the plugin's hooks.json
|
|
66
|
+
// points it at tools/query-logger.cjs, vendored to .forge/tools/ by Step 3.
|
|
67
|
+
type: "command",
|
|
68
|
+
command: 'node "$CLAUDE_PROJECT_DIR/.forge/tools/query-logger.cjs"',
|
|
69
|
+
timeout: 3000,
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
type: "command",
|
|
73
|
+
command: 'node "$CLAUDE_PROJECT_DIR/.forge/tools/hooks/post-init.cjs"',
|
|
74
|
+
timeout: 10000,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
type: "command",
|
|
78
|
+
command: 'node "$CLAUDE_PROJECT_DIR/.forge/tools/hooks/post-sprint.cjs"',
|
|
79
|
+
timeout: 10000,
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
PermissionRequest: [
|
|
85
|
+
{
|
|
86
|
+
matcher: "Bash|Write|Edit|MultiEdit|WebFetch",
|
|
87
|
+
hooks: [
|
|
88
|
+
{
|
|
89
|
+
type: "command",
|
|
90
|
+
command: 'node "$CLAUDE_PROJECT_DIR/.forge/tools/hooks/forge-permissions.cjs"',
|
|
91
|
+
timeout: 3000,
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
// ── Private helpers ───────────────────────────────────────────────────────────
|
|
99
|
+
/**
|
|
100
|
+
* Checks whether a hooks block already contains Forge entries by looking for
|
|
101
|
+
* the Forge hook sentinel string in any hook command.
|
|
102
|
+
*/
|
|
103
|
+
function hasForgeEntries(hooks) {
|
|
104
|
+
for (const eventGroups of Object.values(hooks)) {
|
|
105
|
+
if (!Array.isArray(eventGroups))
|
|
106
|
+
continue;
|
|
107
|
+
for (const group of eventGroups) {
|
|
108
|
+
const g = group;
|
|
109
|
+
if (!Array.isArray(g.hooks))
|
|
110
|
+
continue;
|
|
111
|
+
for (const hook of g.hooks) {
|
|
112
|
+
if (typeof hook.command === "string" && hook.command.includes(FORGE_HOOK_SENTINEL)) {
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Merge Forge hooks into an existing hooks object.
|
|
122
|
+
* For each event type, Forge entries are appended only where no Forge entry already exists
|
|
123
|
+
* (detected by FORGE_HOOK_SENTINEL in command string).
|
|
124
|
+
*/
|
|
125
|
+
function mergeHooksIntoExisting(existing, forgeBlock) {
|
|
126
|
+
const merged = { ...existing };
|
|
127
|
+
const eventTypes = ["SessionStart", "PreToolUse", "PostToolUse", "PermissionRequest"];
|
|
128
|
+
for (const eventType of eventTypes) {
|
|
129
|
+
const forgeGroups = forgeBlock[eventType];
|
|
130
|
+
if (!forgeGroups || !Array.isArray(forgeGroups))
|
|
131
|
+
continue;
|
|
132
|
+
const existingGroups = Array.isArray(merged[eventType]) ? [...merged[eventType]] : [];
|
|
133
|
+
// Check if any existing group already has Forge entries for this event type
|
|
134
|
+
const alreadyHasForge = existingGroups.some((group) => {
|
|
135
|
+
const g = group;
|
|
136
|
+
return (Array.isArray(g.hooks) &&
|
|
137
|
+
g.hooks.some((h) => typeof h.command === "string" && h.command.includes(FORGE_HOOK_SENTINEL)));
|
|
138
|
+
});
|
|
139
|
+
if (!alreadyHasForge) {
|
|
140
|
+
// Append Forge groups for this event type
|
|
141
|
+
existingGroups.push(...forgeGroups);
|
|
142
|
+
}
|
|
143
|
+
merged[eventType] = existingGroups;
|
|
144
|
+
}
|
|
145
|
+
return merged;
|
|
146
|
+
}
|
|
147
|
+
// ── mergeForgeHooks ───────────────────────────────────────────────────────────
|
|
148
|
+
/**
|
|
149
|
+
* Merge Forge hooks into the project .claude/settings.json with merge-not-clobber semantics.
|
|
150
|
+
*
|
|
151
|
+
* Rules:
|
|
152
|
+
* 1. File absent → create with { "hooks": <forgeHooksBlock> }.
|
|
153
|
+
* 2. File present, valid JSON, no hooks key → add hooks key, preserve all other keys.
|
|
154
|
+
* 3. File present, valid JSON, hooks key with all Forge entries already present → "already-present" (no-op).
|
|
155
|
+
* 4. File present, valid JSON, hooks key with unrelated or missing Forge entries →
|
|
156
|
+
* merge Forge entries, preserve all existing non-Forge entries.
|
|
157
|
+
* 5. File present, malformed JSON → "error", file never overwritten.
|
|
158
|
+
* 6. Write failure → "error" with descriptive warning.
|
|
159
|
+
*/
|
|
160
|
+
export function mergeForgeHooks(settingsPath) {
|
|
161
|
+
const forgeBlock = buildForgeHooksBlock();
|
|
162
|
+
// ── Branch 1: File absent ─────────────────────────────────────────────────
|
|
163
|
+
if (!fs.existsSync(settingsPath)) {
|
|
164
|
+
try {
|
|
165
|
+
const content = JSON.stringify({ hooks: forgeBlock }, null, 2) + "\n";
|
|
166
|
+
fs.writeFileSync(settingsPath, content, "utf8");
|
|
167
|
+
return { outcome: "created" };
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
const e = err;
|
|
171
|
+
return {
|
|
172
|
+
outcome: "error",
|
|
173
|
+
warning: `Could not write ${settingsPath}: ${e.message ?? String(err)}`,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// ── File present: read and parse ──────────────────────────────────────────
|
|
178
|
+
let rawContent;
|
|
179
|
+
try {
|
|
180
|
+
rawContent = fs.readFileSync(settingsPath, "utf8");
|
|
181
|
+
}
|
|
182
|
+
catch (err) {
|
|
183
|
+
const e = err;
|
|
184
|
+
return {
|
|
185
|
+
outcome: "error",
|
|
186
|
+
warning: `Could not read ${settingsPath}: ${e.message ?? String(err)}`,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
// ── Branch 2 / 7: Empty or malformed JSON ────────────────────────────────
|
|
190
|
+
if (rawContent.trim() === "") {
|
|
191
|
+
return {
|
|
192
|
+
outcome: "error",
|
|
193
|
+
warning: `${settingsPath} is empty — cannot parse as JSON. Fix or delete the file and re-run.`,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
let parsed;
|
|
197
|
+
try {
|
|
198
|
+
parsed = JSON.parse(rawContent);
|
|
199
|
+
}
|
|
200
|
+
catch (err) {
|
|
201
|
+
const e = err;
|
|
202
|
+
return {
|
|
203
|
+
outcome: "error",
|
|
204
|
+
warning: `${settingsPath} contains malformed JSON — will not overwrite. Parse error: ${e.message ?? String(err)}`,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
// ── Branch 3: Empty object {} (no hooks key) ──────────────────────────────
|
|
208
|
+
// ── Branch 4: Has other keys but no hooks key ─────────────────────────────
|
|
209
|
+
if (!("hooks" in parsed)) {
|
|
210
|
+
try {
|
|
211
|
+
const updated = { ...parsed, hooks: forgeBlock };
|
|
212
|
+
const content = JSON.stringify(updated, null, 2) + "\n";
|
|
213
|
+
fs.writeFileSync(settingsPath, content, "utf8");
|
|
214
|
+
return { outcome: "merged" };
|
|
215
|
+
}
|
|
216
|
+
catch (err) {
|
|
217
|
+
const e = err;
|
|
218
|
+
return {
|
|
219
|
+
outcome: "error",
|
|
220
|
+
warning: `Could not write ${settingsPath}: ${e.message ?? String(err)}`,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
// ── Branch 6: Already has Forge entries ───────────────────────────────────
|
|
225
|
+
const existingHooks = parsed.hooks;
|
|
226
|
+
if (hasForgeEntries(existingHooks)) {
|
|
227
|
+
return { outcome: "already-present" };
|
|
228
|
+
}
|
|
229
|
+
// ── Branch 5: Has hooks key with unrelated entries — merge Forge in ───────
|
|
230
|
+
try {
|
|
231
|
+
const mergedHooks = mergeHooksIntoExisting(existingHooks, forgeBlock);
|
|
232
|
+
const updated = { ...parsed, hooks: mergedHooks };
|
|
233
|
+
const content = JSON.stringify(updated, null, 2) + "\n";
|
|
234
|
+
fs.writeFileSync(settingsPath, content, "utf8");
|
|
235
|
+
return { outcome: "merged" };
|
|
236
|
+
}
|
|
237
|
+
catch (err) {
|
|
238
|
+
const e = err;
|
|
239
|
+
return {
|
|
240
|
+
outcome: "error",
|
|
241
|
+
warning: `Could not write ${settingsPath}: ${e.message ?? String(err)}`,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
//# sourceMappingURL=settings-merge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings-merge.js","sourceRoot":"","sources":["../../../../src/extensions/forgecli/claude-bootstrap/settings-merge.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface UninstallOptions {
|
|
2
|
+
/** Target project root (absolute path). */
|
|
3
|
+
dir: string;
|
|
4
|
+
/** Absolute path to the dist/forge-payload/ directory (to re-derive vendored names). */
|
|
5
|
+
payloadRoot: string;
|
|
6
|
+
/**
|
|
7
|
+
* When true, ALSO remove user data: .forge/config.json and .forge/store/**,
|
|
8
|
+
* then the now-empty .forge/ itself. The KB folder is never auto-removed.
|
|
9
|
+
*/
|
|
10
|
+
purge: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface UninstallResult {
|
|
13
|
+
ok: boolean;
|
|
14
|
+
/** True if a .forge/.bootstrap-manifest.json was found (a forge-bootstrapped project). */
|
|
15
|
+
bootstrapped: boolean;
|
|
16
|
+
/** Paths removed this run (files and dirs). */
|
|
17
|
+
removed: string[];
|
|
18
|
+
/** User-data paths deliberately preserved (only meaningful when purge=false). */
|
|
19
|
+
kept: string[];
|
|
20
|
+
/** Non-fatal issues encountered. */
|
|
21
|
+
warnings: string[];
|
|
22
|
+
}
|
|
23
|
+
export declare function uninstallClaudeProject(opts: UninstallOptions): UninstallResult;
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
// claude-bootstrap/uninstall.ts — deterministic reverse of bootstrapClaudeProject.
|
|
2
|
+
//
|
|
3
|
+
// `4ge uninstall claude [dir]` removes what `4ge init claude` scaffolded:
|
|
4
|
+
// the vendored Forge scaffold (tools, hooks, schemas, commands, forge-root
|
|
5
|
+
// content, agents/skills, workflow drivers, bootstrap manifest), un-merges the
|
|
6
|
+
// Forge hooks from .claude/settings.json, and un-appends the Forge block from
|
|
7
|
+
// .gitignore. Pure fs; zero LLM tokens, zero network.
|
|
8
|
+
//
|
|
9
|
+
// Safety contract:
|
|
10
|
+
// - PRESERVES user data by default: .forge/config.json, .forge/store/** (the
|
|
11
|
+
// SDLC store — sprints/tasks/bugs/events), and the KB folder are NEVER
|
|
12
|
+
// touched unless `purge` is set.
|
|
13
|
+
// - REFUSES to act when no .forge/.bootstrap-manifest.json is present — that
|
|
14
|
+
// manifest is the proof a project was forge-bootstrapped. Without it we will
|
|
15
|
+
// not delete anything (avoids nuking a hand-built .claude/ or .forge/).
|
|
16
|
+
// - settings.json and .gitignore are EDITED (un-merge / un-append), never
|
|
17
|
+
// blindly deleted — they may carry non-Forge content.
|
|
18
|
+
// - Idempotent: a second run finds nothing to remove and reports cleanly.
|
|
19
|
+
import * as fs from "node:fs";
|
|
20
|
+
import * as path from "node:path";
|
|
21
|
+
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
22
|
+
/** Sentinel identifying Forge hook commands in settings.json (mirrors settings-merge). */
|
|
23
|
+
const FORGE_HOOK_SENTINEL = ".forge/tools/";
|
|
24
|
+
/** Exact block appended to .gitignore by bootstrap (mirrors bootstrap.ts). */
|
|
25
|
+
const GITIGNORE_APPEND_BLOCK = "\n# Forge — transient agent event logs (one file per phase, do not commit)\n.forge/store/events/\n";
|
|
26
|
+
// ── Private helpers ───────────────────────────────────────────────────────────
|
|
27
|
+
/** Remove a file or directory if it exists; record the path. Never throws. */
|
|
28
|
+
function rmIfExists(target, removed, warnings) {
|
|
29
|
+
if (!fs.existsSync(target))
|
|
30
|
+
return;
|
|
31
|
+
try {
|
|
32
|
+
fs.rmSync(target, { recursive: true, force: true });
|
|
33
|
+
removed.push(target);
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
const e = err;
|
|
37
|
+
warnings.push(`could not remove ${target}: ${e.message ?? String(err)}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/** Remove a directory only if it exists and is empty. Records if removed. */
|
|
41
|
+
function rmDirIfEmpty(dirPath, removed) {
|
|
42
|
+
if (!fs.existsSync(dirPath))
|
|
43
|
+
return;
|
|
44
|
+
try {
|
|
45
|
+
if (fs.readdirSync(dirPath).length === 0) {
|
|
46
|
+
fs.rmdirSync(dirPath);
|
|
47
|
+
removed.push(dirPath);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// non-fatal — leave it
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** List immediate entry names in a payload subdir, or [] if absent. */
|
|
55
|
+
function payloadEntryNames(payloadRoot, sub) {
|
|
56
|
+
const src = path.join(payloadRoot, sub);
|
|
57
|
+
if (!fs.existsSync(src))
|
|
58
|
+
return [];
|
|
59
|
+
try {
|
|
60
|
+
return fs.readdirSync(src);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return [];
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Un-merge Forge hooks from .claude/settings.json.
|
|
68
|
+
* Removes hook entries whose command targets `.forge/tools/…`, drops groups and
|
|
69
|
+
* event-type keys that become empty, and deletes the file only if it is left
|
|
70
|
+
* with no keys at all (i.e. Forge created it). Never touches malformed JSON.
|
|
71
|
+
*/
|
|
72
|
+
function removeForgeHooks(settingsPath, removed, warnings) {
|
|
73
|
+
if (!fs.existsSync(settingsPath))
|
|
74
|
+
return;
|
|
75
|
+
let parsed;
|
|
76
|
+
try {
|
|
77
|
+
parsed = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
const e = err;
|
|
81
|
+
warnings.push(`settings.json not valid JSON — left untouched: ${e.message ?? String(err)}`);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const hooks = parsed.hooks;
|
|
85
|
+
if (!hooks || typeof hooks !== "object")
|
|
86
|
+
return;
|
|
87
|
+
let changed = false;
|
|
88
|
+
for (const [eventType, groupsRaw] of Object.entries(hooks)) {
|
|
89
|
+
if (!Array.isArray(groupsRaw))
|
|
90
|
+
continue;
|
|
91
|
+
const keptGroups = [];
|
|
92
|
+
for (const group of groupsRaw) {
|
|
93
|
+
const g = group;
|
|
94
|
+
if (Array.isArray(g.hooks)) {
|
|
95
|
+
const keptHooks = g.hooks.filter((h) => !(typeof h.command === "string" && h.command.includes(FORGE_HOOK_SENTINEL)));
|
|
96
|
+
if (keptHooks.length !== g.hooks.length)
|
|
97
|
+
changed = true;
|
|
98
|
+
if (keptHooks.length === 0)
|
|
99
|
+
continue; // drop emptied group
|
|
100
|
+
keptGroups.push({ ...g, hooks: keptHooks });
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
keptGroups.push(group);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (keptGroups.length === 0) {
|
|
107
|
+
delete hooks[eventType];
|
|
108
|
+
changed = true;
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
hooks[eventType] = keptGroups;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (!changed)
|
|
115
|
+
return;
|
|
116
|
+
// Drop an emptied hooks key, then delete the file if nothing else remains.
|
|
117
|
+
if (Object.keys(hooks).length === 0)
|
|
118
|
+
delete parsed.hooks;
|
|
119
|
+
if (Object.keys(parsed).length === 0) {
|
|
120
|
+
rmIfExists(settingsPath, removed, warnings);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
fs.writeFileSync(settingsPath, JSON.stringify(parsed, null, 2) + "\n", "utf8");
|
|
125
|
+
removed.push(`${settingsPath} (Forge hooks un-merged)`);
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
const e = err;
|
|
129
|
+
warnings.push(`could not rewrite ${settingsPath}: ${e.message ?? String(err)}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/** Remove the exact Forge block appended to .gitignore by bootstrap. */
|
|
133
|
+
function removeGitignoreBlock(gitignorePath, removed, warnings) {
|
|
134
|
+
if (!fs.existsSync(gitignorePath))
|
|
135
|
+
return;
|
|
136
|
+
let content;
|
|
137
|
+
try {
|
|
138
|
+
content = fs.readFileSync(gitignorePath, "utf8");
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
if (!content.includes(GITIGNORE_APPEND_BLOCK))
|
|
144
|
+
return;
|
|
145
|
+
try {
|
|
146
|
+
fs.writeFileSync(gitignorePath, content.replace(GITIGNORE_APPEND_BLOCK, ""), "utf8");
|
|
147
|
+
removed.push(`${gitignorePath} (Forge block un-appended)`);
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
const e = err;
|
|
151
|
+
warnings.push(`could not rewrite ${gitignorePath}: ${e.message ?? String(err)}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// ── Main export ───────────────────────────────────────────────────────────────
|
|
155
|
+
export function uninstallClaudeProject(opts) {
|
|
156
|
+
const { dir, payloadRoot, purge } = opts;
|
|
157
|
+
const removed = [];
|
|
158
|
+
const kept = [];
|
|
159
|
+
const warnings = [];
|
|
160
|
+
// ── Step 0: Require the bootstrap manifest (proof of a forge bootstrap) ───
|
|
161
|
+
const manifestPath = path.join(dir, ".forge", ".bootstrap-manifest.json");
|
|
162
|
+
if (!fs.existsSync(manifestPath)) {
|
|
163
|
+
warnings.push(`Not a forge-bootstrapped project: ${path.join(".forge", ".bootstrap-manifest.json")} not found in ${dir}. ` +
|
|
164
|
+
"Refusing to remove anything (run `4ge init claude` first, or remove files by hand).");
|
|
165
|
+
return { ok: false, bootstrapped: false, removed, kept, warnings };
|
|
166
|
+
}
|
|
167
|
+
const forgeDir = path.join(dir, ".forge");
|
|
168
|
+
const claudeDir = path.join(dir, ".claude");
|
|
169
|
+
// ── Step 1: Remove Forge-owned .forge/ scaffold (preserve config + store) ──
|
|
170
|
+
for (const sub of ["tools", "schemas", "init", ".base-pack", "meta", ".claude-plugin", "cache"]) {
|
|
171
|
+
rmIfExists(path.join(forgeDir, sub), removed, warnings);
|
|
172
|
+
}
|
|
173
|
+
rmIfExists(manifestPath, removed, warnings);
|
|
174
|
+
// ── Step 2: Remove Forge command surface ──────────────────────────────────
|
|
175
|
+
rmIfExists(path.join(claudeDir, "commands", "forge"), removed, warnings);
|
|
176
|
+
rmDirIfEmpty(path.join(claudeDir, "commands"), removed);
|
|
177
|
+
// ── Step 3: Remove workflow drivers (.claude/workflows is forge-exclusive) ─
|
|
178
|
+
rmIfExists(path.join(claudeDir, "workflows"), removed, warnings);
|
|
179
|
+
// ── Step 4: Remove vendored agents/skills — only the ones bootstrap added ──
|
|
180
|
+
// Re-derive the exact entry names from the payload so user-authored agents
|
|
181
|
+
// or skills sharing the directory are preserved.
|
|
182
|
+
for (const asset of ["agents", "skills"]) {
|
|
183
|
+
const assetDir = path.join(claudeDir, asset);
|
|
184
|
+
if (!fs.existsSync(assetDir))
|
|
185
|
+
continue;
|
|
186
|
+
for (const name of payloadEntryNames(payloadRoot, asset)) {
|
|
187
|
+
rmIfExists(path.join(assetDir, name), removed, warnings);
|
|
188
|
+
}
|
|
189
|
+
rmDirIfEmpty(assetDir, removed);
|
|
190
|
+
}
|
|
191
|
+
// ── Step 5: Un-merge settings hooks + un-append .gitignore (edit, not delete)
|
|
192
|
+
removeForgeHooks(path.join(claudeDir, "settings.json"), removed, warnings);
|
|
193
|
+
removeGitignoreBlock(path.join(dir, ".gitignore"), removed, warnings);
|
|
194
|
+
// ── Step 6: User data — preserve by default, remove only on --purge ───────
|
|
195
|
+
const configPath = path.join(forgeDir, "config.json");
|
|
196
|
+
const storePath = path.join(forgeDir, "store");
|
|
197
|
+
if (purge) {
|
|
198
|
+
rmIfExists(storePath, removed, warnings);
|
|
199
|
+
rmIfExists(configPath, removed, warnings);
|
|
200
|
+
// The KB folder (paths.engineering) is intentionally NOT auto-removed even
|
|
201
|
+
// under --purge: it is the user's generated documentation, not scaffold.
|
|
202
|
+
warnings.push("--purge removed .forge/store and .forge/config.json. The KB folder was left intact (remove it by hand if intended).");
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
if (fs.existsSync(configPath))
|
|
206
|
+
kept.push(configPath);
|
|
207
|
+
if (fs.existsSync(storePath))
|
|
208
|
+
kept.push(storePath);
|
|
209
|
+
}
|
|
210
|
+
// ── Step 7: Tidy empty parents ────────────────────────────────────────────
|
|
211
|
+
rmDirIfEmpty(forgeDir, removed);
|
|
212
|
+
rmDirIfEmpty(claudeDir, removed);
|
|
213
|
+
return { ok: true, bootstrapped: true, removed, kept, warnings };
|
|
214
|
+
}
|
|
215
|
+
//# sourceMappingURL=uninstall.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"uninstall.js","sourceRoot":"","sources":["../../../../src/extensions/forgecli/claude-bootstrap/uninstall.ts"],"names":[],"mappings":"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"}
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
import { matchesKey, Key, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
32
32
|
import { getSessionRegistry } from "../session-registry.js";
|
|
33
33
|
import { buildViewModel } from "./view-model.js";
|
|
34
|
-
import {
|
|
34
|
+
import { fmtTokenFooter, toDisplayLines } from "../viewport/renderer.js";
|
|
35
35
|
import { paintTailLine } from "../viewport/theme.js";
|
|
36
36
|
import { cursor, nodeGlyph, statusLabel, dim, accentBold, warn, bold, border, collapseIndicator, promptExpandIcon, truncateLines, } from "./theme.js";
|
|
37
37
|
// ── Word-wrap helper ───────────────────────────────────────────────────────────
|
|
@@ -662,17 +662,16 @@ export class DashboardComponent {
|
|
|
662
662
|
const aggUsage = this.controller.getAggregateUsage();
|
|
663
663
|
const aggCompression = this.controller.getAggregateCompression();
|
|
664
664
|
const activeModel = this.controller.getActiveModel();
|
|
665
|
-
const meter =
|
|
666
|
-
const compSuffix = aggCompression.tokensSaved > 0 ? ` ⇌${aggCompression.tokensSaved}t` : "";
|
|
665
|
+
const meter = fmtTokenFooter(aggUsage, aggCompression.tokensSaved > 0 ? aggCompression : undefined);
|
|
667
666
|
const modelLabel = activeModel?.provider && activeModel?.model
|
|
668
667
|
? `${activeModel.provider} ${activeModel.model}`
|
|
669
668
|
: (activeModel?.provider ?? activeModel?.model ?? "");
|
|
670
669
|
let footerLine = "";
|
|
671
670
|
if (modelLabel && meter) {
|
|
672
|
-
footerLine = `${hintsBase} ${dim(modelLabel, this.theme)} Σ ${meter}
|
|
671
|
+
footerLine = `${hintsBase} ${dim(modelLabel, this.theme)} Σ ${meter}`;
|
|
673
672
|
}
|
|
674
673
|
else if (meter) {
|
|
675
|
-
footerLine = `${hintsBase} Σ ${meter}
|
|
674
|
+
footerLine = `${hintsBase} Σ ${meter}`;
|
|
676
675
|
}
|
|
677
676
|
else if (modelLabel) {
|
|
678
677
|
footerLine = `${hintsBase} ${dim(modelLabel, this.theme)}`;
|
|
@@ -800,7 +799,11 @@ export class DashboardComponent {
|
|
|
800
799
|
// errors, newlines and all); ALL display normalization happens here:
|
|
801
800
|
// toDisplayLines splits/sanitizes, wrapLine fits the pane width, and
|
|
802
801
|
// long entries clamp to LOG_CLAMP_ROWS unless expanded (ctrl+o).
|
|
803
|
-
|
|
802
|
+
// Rendered for ALL node kinds: leaf nodes carry subagent tool-call
|
|
803
|
+
// activity (via the viewport observer); orchestrator-kind root nodes
|
|
804
|
+
// carry the orchestrator's own decision log (gates, verdicts,
|
|
805
|
+
// escalations, halts) mirrored from its notify stream.
|
|
806
|
+
if (node.tailBuffer.length > 0) {
|
|
804
807
|
const expanded = this.controller.getState().logExpanded;
|
|
805
808
|
const total = node.tailBuffer.length;
|
|
806
809
|
const modeHint = expanded ? "^o clamp" : "^o expand";
|
|
@@ -908,7 +911,7 @@ export class DashboardComponent {
|
|
|
908
911
|
formatMetrics(node) {
|
|
909
912
|
const parts = [];
|
|
910
913
|
if (node.usage.input || node.usage.output || node.usage.cacheRead) {
|
|
911
|
-
parts.push(
|
|
914
|
+
parts.push(fmtTokenFooter(node.usage));
|
|
912
915
|
}
|
|
913
916
|
if (node.metrics.toolCount) {
|
|
914
917
|
parts.push(`${node.metrics.toolCount} tool${node.metrics.toolCount === 1 ? "" : "s"}`);
|