@entelligentsia/forgecli 0.10.0 → 0.11.2
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 +95 -0
- package/README.md +21 -3
- package/dist/CHANGELOG-forge-plugin.md +90 -0
- package/dist/bin/config.js +6 -0
- package/dist/bin/config.js.map +1 -1
- package/dist/extensions/forgecli/add-pipeline.d.ts +19 -0
- package/dist/extensions/forgecli/add-pipeline.js +143 -0
- package/dist/extensions/forgecli/add-pipeline.js.map +1 -0
- package/dist/extensions/forgecli/add-task.d.ts +20 -0
- package/dist/extensions/forgecli/add-task.js +154 -0
- package/dist/extensions/forgecli/add-task.js.map +1 -0
- package/dist/extensions/forgecli/calibrate.d.ts +61 -0
- package/dist/extensions/forgecli/calibrate.js +488 -0
- package/dist/extensions/forgecli/calibrate.js.map +1 -0
- package/dist/extensions/forgecli/fix-bug.d.ts +9 -1
- package/dist/extensions/forgecli/fix-bug.js +155 -45
- package/dist/extensions/forgecli/fix-bug.js.map +1 -1
- package/dist/extensions/forgecli/forge-commands.js +15 -22
- package/dist/extensions/forgecli/forge-commands.js.map +1 -1
- package/dist/extensions/forgecli/forge-subagent.d.ts +16 -1
- package/dist/extensions/forgecli/forge-subagent.js +45 -8
- package/dist/extensions/forgecli/forge-subagent.js.map +1 -1
- package/dist/extensions/forgecli/forge-update-command.d.ts +9 -0
- package/dist/extensions/forgecli/forge-update-command.js +106 -7
- package/dist/extensions/forgecli/forge-update-command.js.map +1 -1
- package/dist/extensions/forgecli/health-check.d.ts +22 -1
- package/dist/extensions/forgecli/health-check.js +177 -4
- package/dist/extensions/forgecli/health-check.js.map +1 -1
- package/dist/extensions/forgecli/hook-dispatcher.d.ts +25 -1
- package/dist/extensions/forgecli/hook-dispatcher.js +104 -9
- package/dist/extensions/forgecli/hook-dispatcher.js.map +1 -1
- package/dist/extensions/forgecli/hooks/check-update.d.ts +81 -0
- package/dist/extensions/forgecli/hooks/check-update.js +308 -0
- package/dist/extensions/forgecli/hooks/check-update.js.map +1 -0
- package/dist/extensions/forgecli/hooks/forge-permissions.d.ts +32 -0
- package/dist/extensions/forgecli/hooks/forge-permissions.js +119 -0
- package/dist/extensions/forgecli/hooks/forge-permissions.js.map +1 -0
- package/dist/extensions/forgecli/hooks/triage-error.d.ts +23 -0
- package/dist/extensions/forgecli/hooks/triage-error.js +62 -0
- package/dist/extensions/forgecli/hooks/triage-error.js.map +1 -0
- package/dist/extensions/forgecli/hooks/write-guard.d.ts +28 -0
- package/dist/extensions/forgecli/hooks/write-guard.js +225 -0
- package/dist/extensions/forgecli/hooks/write-guard.js.map +1 -0
- package/dist/extensions/forgecli/index.js +60 -0
- package/dist/extensions/forgecli/index.js.map +1 -1
- package/dist/extensions/forgecli/init-context.d.ts +1 -1
- package/dist/extensions/forgecli/init-context.js +21 -6
- package/dist/extensions/forgecli/init-context.js.map +1 -1
- package/dist/extensions/forgecli/materialize.d.ts +16 -0
- package/dist/extensions/forgecli/materialize.js +195 -0
- package/dist/extensions/forgecli/materialize.js.map +1 -0
- package/dist/extensions/forgecli/migrate.d.ts +19 -0
- package/dist/extensions/forgecli/migrate.js +258 -0
- package/dist/extensions/forgecli/migrate.js.map +1 -0
- package/dist/extensions/forgecli/migration-engine.d.ts +111 -0
- package/dist/extensions/forgecli/migration-engine.js +533 -0
- package/dist/extensions/forgecli/migration-engine.js.map +1 -0
- package/dist/extensions/forgecli/quiz-agent.d.ts +17 -0
- package/dist/extensions/forgecli/quiz-agent.js +98 -0
- package/dist/extensions/forgecli/quiz-agent.js.map +1 -0
- package/dist/extensions/forgecli/remove-command.d.ts +17 -0
- package/dist/extensions/forgecli/remove-command.js +124 -0
- package/dist/extensions/forgecli/remove-command.js.map +1 -0
- package/dist/extensions/forgecli/report-bug.d.ts +25 -0
- package/dist/extensions/forgecli/report-bug.js +159 -0
- package/dist/extensions/forgecli/report-bug.js.map +1 -0
- package/dist/extensions/forgecli/retrospective.d.ts +19 -0
- package/dist/extensions/forgecli/retrospective.js +156 -0
- package/dist/extensions/forgecli/retrospective.js.map +1 -0
- package/dist/extensions/forgecli/run-sprint.js +36 -3
- package/dist/extensions/forgecli/run-sprint.js.map +1 -1
- package/dist/extensions/forgecli/run-task.d.ts +9 -1
- package/dist/extensions/forgecli/run-task.js +66 -13
- package/dist/extensions/forgecli/run-task.js.map +1 -1
- package/dist/extensions/forgecli/session-registry.d.ts +40 -2
- package/dist/extensions/forgecli/session-registry.js +71 -1
- package/dist/extensions/forgecli/session-registry.js.map +1 -1
- package/dist/extensions/forgecli/status-command.d.ts +19 -0
- package/dist/extensions/forgecli/status-command.js +140 -0
- package/dist/extensions/forgecli/status-command.js.map +1 -0
- package/dist/extensions/forgecli/store-query.d.ts +22 -0
- package/dist/extensions/forgecli/store-query.js +107 -0
- package/dist/extensions/forgecli/store-query.js.map +1 -0
- package/dist/extensions/forgecli/store-repair.d.ts +17 -0
- package/dist/extensions/forgecli/store-repair.js +123 -0
- package/dist/extensions/forgecli/store-repair.js.map +1 -0
- package/dist/extensions/forgecli/test-orchestrate.js +1 -0
- package/dist/extensions/forgecli/test-orchestrate.js.map +1 -1
- package/dist/extensions/forgecli/thread-switcher.js +286 -41
- package/dist/extensions/forgecli/thread-switcher.js.map +1 -1
- package/dist/extensions/forgecli/transition-guard.js +7 -2
- package/dist/extensions/forgecli/transition-guard.js.map +1 -1
- package/dist/extensions/forgecli/update-tools.d.ts +23 -0
- package/dist/extensions/forgecli/update-tools.js +136 -0
- package/dist/extensions/forgecli/update-tools.js.map +1 -0
- package/dist/extensions/forgecli/viewport-events.js +10 -0
- package/dist/extensions/forgecli/viewport-events.js.map +1 -1
- package/dist/extensions/forgecli/viewport-renderer.d.ts +18 -0
- package/dist/extensions/forgecli/viewport-renderer.js +27 -0
- package/dist/extensions/forgecli/viewport-renderer.js.map +1 -1
- package/dist/extensions/forgecli/viewport-theme.js +4 -0
- package/dist/extensions/forgecli/viewport-theme.js.map +1 -1
- package/dist/extensions/forgecli/whats-new-widget.d.ts +13 -8
- package/dist/extensions/forgecli/whats-new-widget.js +111 -42
- package/dist/extensions/forgecli/whats-new-widget.js.map +1 -1
- package/dist/forge-payload/.base-pack/workflows/architect_approve.md +29 -3
- package/dist/forge-payload/.base-pack/workflows/commit_task.md +15 -8
- package/dist/forge-payload/.base-pack/workflows/fix_bug.md +327 -185
- package/dist/forge-payload/.base-pack/workflows/implement_plan.md +18 -10
- package/dist/forge-payload/.base-pack/workflows/plan_task.md +15 -9
- package/dist/forge-payload/.base-pack/workflows/review_code.md +14 -6
- package/dist/forge-payload/.base-pack/workflows/review_plan.md +18 -10
- package/dist/forge-payload/.claude-plugin/plugin.json +1 -1
- package/dist/forge-payload/.schemas/bug.schema.json +3 -2
- package/dist/forge-payload/.schemas/config.schema.json +83 -0
- package/dist/forge-payload/.schemas/migrations.json +2049 -0
- package/dist/forge-payload/commands/regenerate.md +17 -1
- package/dist/forge-payload/meta/personas/README.md +16 -0
- package/dist/forge-payload/meta/personas/meta-architect.md +70 -0
- package/dist/forge-payload/meta/personas/meta-bug-fixer.md +73 -0
- package/dist/forge-payload/meta/personas/meta-collator.md +72 -0
- package/dist/forge-payload/meta/personas/meta-engineer.md +70 -0
- package/dist/forge-payload/meta/personas/meta-orchestrator.md +71 -0
- package/dist/forge-payload/meta/personas/meta-product-manager.md +82 -0
- package/dist/forge-payload/meta/personas/meta-qa-engineer.md +91 -0
- package/dist/forge-payload/meta/personas/meta-supervisor.md +92 -0
- package/dist/forge-payload/meta/skill-recommendations.md +154 -0
- package/dist/forge-payload/meta/skills/meta-architect-skills.md +43 -0
- package/dist/forge-payload/meta/skills/meta-bug-fixer-skills.md +43 -0
- package/dist/forge-payload/meta/skills/meta-collator-skills.md +41 -0
- package/dist/forge-payload/meta/skills/meta-engineer-skills.md +43 -0
- package/dist/forge-payload/meta/skills/meta-generic-skills.md +58 -0
- package/dist/forge-payload/meta/skills/meta-qa-engineer-skills.md +46 -0
- package/dist/forge-payload/meta/skills/meta-supervisor-skills.md +43 -0
- package/dist/forge-payload/meta/store-schema/bug.schema.md +71 -0
- package/dist/forge-payload/meta/store-schema/event.schema.md +76 -0
- package/dist/forge-payload/meta/store-schema/feature.schema.md +65 -0
- package/dist/forge-payload/meta/store-schema/sprint.schema.md +64 -0
- package/dist/forge-payload/meta/store-schema/task.schema.md +78 -0
- package/dist/forge-payload/meta/templates/meta-code-review.md +26 -0
- package/dist/forge-payload/meta/templates/meta-plan-review.md +28 -0
- package/dist/forge-payload/meta/templates/meta-plan.md +28 -0
- package/dist/forge-payload/meta/templates/meta-progress.md +25 -0
- package/dist/forge-payload/meta/templates/meta-retrospective.md +28 -0
- package/dist/forge-payload/meta/templates/meta-sprint-manifest.md +26 -0
- package/dist/forge-payload/meta/templates/meta-sprint-requirements.md +91 -0
- package/dist/forge-payload/meta/templates/meta-task-prompt.md +26 -0
- package/dist/forge-payload/meta/tool-specs/collate.spec.md +88 -0
- package/dist/forge-payload/meta/tool-specs/generation-manifest.spec.md +139 -0
- package/dist/forge-payload/meta/tool-specs/manage-config.spec.md +143 -0
- package/dist/forge-payload/meta/tool-specs/seed-store.spec.md +91 -0
- package/dist/forge-payload/meta/tool-specs/store-cli.spec.md +328 -0
- package/dist/forge-payload/meta/tool-specs/validate-store.spec.md +191 -0
- package/dist/forge-payload/meta/workflows/_fragments/context-injection.md +75 -0
- package/dist/forge-payload/meta/workflows/_fragments/event-emission-schema.md +73 -0
- package/dist/forge-payload/meta/workflows/_fragments/finalize.md +13 -0
- package/dist/forge-payload/meta/workflows/_fragments/friction-emit.md +73 -0
- package/dist/forge-payload/meta/workflows/_fragments/progress-reporting.md +38 -0
- package/dist/forge-payload/meta/workflows/_fragments/store-cli-verbs.md +39 -0
- package/dist/forge-payload/meta/workflows/meta-approve.md +119 -0
- package/dist/forge-payload/meta/workflows/meta-collate.md +89 -0
- package/dist/forge-payload/meta/workflows/meta-commit.md +93 -0
- package/dist/forge-payload/meta/workflows/meta-enhance.md +286 -0
- package/dist/forge-payload/meta/workflows/meta-fix-bug.md +501 -0
- package/dist/forge-payload/meta/workflows/meta-implement.md +132 -0
- package/dist/forge-payload/meta/workflows/meta-migrate.md +455 -0
- package/dist/forge-payload/meta/workflows/meta-orchestrate.md +993 -0
- package/dist/forge-payload/meta/workflows/meta-plan-task.md +133 -0
- package/dist/forge-payload/meta/workflows/meta-quiz-agent.md +135 -0
- package/dist/forge-payload/meta/workflows/meta-retrospective.md +65 -0
- package/dist/forge-payload/meta/workflows/meta-review-implementation.md +119 -0
- package/dist/forge-payload/meta/workflows/meta-review-plan.md +108 -0
- package/dist/forge-payload/meta/workflows/meta-review-sprint-completion.md +65 -0
- package/dist/forge-payload/meta/workflows/meta-sprint-intake.md +76 -0
- package/dist/forge-payload/meta/workflows/meta-sprint-plan.md +147 -0
- package/dist/forge-payload/meta/workflows/meta-update-implementation.md +76 -0
- package/dist/forge-payload/meta/workflows/meta-update-plan.md +76 -0
- package/dist/forge-payload/meta/workflows/meta-validate.md +111 -0
- package/dist/forge-payload/tools/check-structure.cjs +344 -0
- package/dist/forge-payload/tools/collate.cjs +34 -9
- package/dist/forge-payload/tools/list-skills.js +76 -0
- package/dist/forge-payload/tools/parse-gates.cjs +8 -2
- package/dist/forge-payload/tools/store-cli.cjs +56 -11
- package/dist/forge-payload/tools/store.cjs +61 -0
- package/dist/forge-payload/tools/substitute-placeholders.cjs +60 -8
- package/dist/forge-payload/tools/validate-store.cjs +6 -2
- package/dist/forge-payload/tools/verify-integrity.cjs +86 -0
- package/package.json +2 -2
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
// hooks/check-update.ts — Session-start Forge-awareness + project-state sync (FORGE-S23-T05).
|
|
2
|
+
//
|
|
3
|
+
// Port of forge/forge/hooks/check-update.js functions (1), (3), (4), (5) for the
|
|
4
|
+
// pi extension context. Function (2) — version-check throttle (npm + GH dual-probe)
|
|
5
|
+
// — is covered by the existing update-check.ts and is NOT duplicated here.
|
|
6
|
+
//
|
|
7
|
+
// Five functions ported:
|
|
8
|
+
// (1) buildForgeAwarenessMsg — AC#1: forge-awareness context injection
|
|
9
|
+
// (3) syncForgeRootAndRef — AC#2, AC#6: distribution-switch detection + forgeRoot/forgeRef sync
|
|
10
|
+
// (4) scanPluginInstallations — AC#3: multi-plugin scanning
|
|
11
|
+
// buildMultiPluginMsg — AC#3: builds notification from scan results
|
|
12
|
+
// (5) buildPendingMigrationMsg — AC#4: pending-migration state surfacing
|
|
13
|
+
//
|
|
14
|
+
// Key differences from plugin:
|
|
15
|
+
// - No stdout hook protocol: pi has no `{"additionalContext":"..."}` stdout pipe.
|
|
16
|
+
// All surfacing is via ctx.ui.notify() — callers do the notify, this module
|
|
17
|
+
// returns strings (or null) for testability.
|
|
18
|
+
// - Config-authoritative: forge-cli uses paths.forgeRoot from .forge/config.json
|
|
19
|
+
// as truth; no CLAUDE_PLUGIN_ROOT env var.
|
|
20
|
+
// - forgeRef source: reads <paths.forgeRoot>/.claude-plugin/plugin.json at runtime
|
|
21
|
+
// (live version), not PKG_VERSIONS.bundledForgeVersion (baked at build time).
|
|
22
|
+
// - Plugin-level cache (CLAUDE_PLUGIN_DATA/update-check-cache.json): not used.
|
|
23
|
+
// Only the project-level cache (.forge/update-check-cache.json) is read/written.
|
|
24
|
+
//
|
|
25
|
+
// All file I/O is wrapped in try/catch — non-fatal. Session-start must never throw.
|
|
26
|
+
//
|
|
27
|
+
// Plugin parity reference: forge/forge/hooks/check-update.js (read-only — do NOT edit).
|
|
28
|
+
//
|
|
29
|
+
// Iron Laws:
|
|
30
|
+
// IL1 — code only under forge-cli/src/extensions/forgecli/.
|
|
31
|
+
// IL5 — vendored forge is read-only; check-update.js is reference only.
|
|
32
|
+
// IL6 — no shell-string interpolation; no spawn calls; only Node fs built-in.
|
|
33
|
+
import * as fs from "node:fs";
|
|
34
|
+
import * as os from "node:os";
|
|
35
|
+
import * as path from "node:path";
|
|
36
|
+
// ── Distribution detection (ported verbatim from check-update.js) ─────────────
|
|
37
|
+
/**
|
|
38
|
+
* Derives the distribution name from a plugin root path.
|
|
39
|
+
* Ported verbatim from forge/forge/hooks/check-update.js `detectDistribution()`.
|
|
40
|
+
*
|
|
41
|
+
* Returns "forge@skillforge" for skillforge-marketplace installs,
|
|
42
|
+
* "forge@forge" for all others (direct forge installs).
|
|
43
|
+
*/
|
|
44
|
+
export function detectDistribution(root) {
|
|
45
|
+
return root.includes("/cache/skillforge/forge/") || root.includes("/marketplaces/skillforge/forge/")
|
|
46
|
+
? "forge@skillforge"
|
|
47
|
+
: "forge@forge";
|
|
48
|
+
}
|
|
49
|
+
// ── Project cache I/O ─────────────────────────────────────────────────────────
|
|
50
|
+
/**
|
|
51
|
+
* Reads and parses the project-level update-check cache from .forge/update-check-cache.json.
|
|
52
|
+
* Returns null if the file is absent or malformed.
|
|
53
|
+
* Non-fatal — all errors are swallowed.
|
|
54
|
+
*/
|
|
55
|
+
export function readProjectCache(forgeDir) {
|
|
56
|
+
try {
|
|
57
|
+
const cacheFile = path.join(forgeDir, "update-check-cache.json");
|
|
58
|
+
const raw = fs.readFileSync(cacheFile, "utf8");
|
|
59
|
+
const parsed = JSON.parse(raw);
|
|
60
|
+
return parsed;
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// ── Function (1): Forge-awareness context injection ───────────────────────────
|
|
67
|
+
/**
|
|
68
|
+
* Builds a Forge-awareness message if this project has a .forge/config.json.
|
|
69
|
+
* Corresponds to the forge-awareness context injection in check-update.js.
|
|
70
|
+
*
|
|
71
|
+
* In the plugin, this is emitted as {"additionalContext":"..."} to stdout.
|
|
72
|
+
* In forge-cli, the caller does ctx.ui.notify(msg, "info") — pi has no
|
|
73
|
+
* session_start additionalContext protocol.
|
|
74
|
+
*
|
|
75
|
+
* Returns null when configPath is missing or unreadable (e.g. outside-Forge project).
|
|
76
|
+
*/
|
|
77
|
+
export function buildForgeAwarenessMsg(configPath) {
|
|
78
|
+
try {
|
|
79
|
+
if (!fs.existsSync(configPath))
|
|
80
|
+
return null;
|
|
81
|
+
return ("This project uses Forge AI-SDLC. Engineering knowledge base: engineering/. " +
|
|
82
|
+
"Generated workflows: .forge/workflows/. Sprint and task store: .forge/store/. " +
|
|
83
|
+
"Use the project slash commands (/plan, /implement, /sprint-plan) to drive development. " +
|
|
84
|
+
"Run /forge:health to check knowledge base currency.");
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// ── Plugin enabled check (ported from check-update.js) ───────────────────────
|
|
91
|
+
/**
|
|
92
|
+
* Returns true if the Forge plugin is enabled in user/project settings.
|
|
93
|
+
* Mirrors isPluginEnabled() from check-update.js.
|
|
94
|
+
*/
|
|
95
|
+
function isPluginEnabled(pluginPath, scope, homeDir, cwd) {
|
|
96
|
+
try {
|
|
97
|
+
const userSettingsPath = path.join(homeDir, ".claude", "settings.json");
|
|
98
|
+
if (fs.existsSync(userSettingsPath)) {
|
|
99
|
+
const userSettings = JSON.parse(fs.readFileSync(userSettingsPath, "utf8"));
|
|
100
|
+
if (userSettings.disablePlugin === true)
|
|
101
|
+
return false;
|
|
102
|
+
const plugins = userSettings.plugins;
|
|
103
|
+
if (plugins && plugins.forge === false)
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
const projectSettingsPath = path.join(cwd, ".claude", "settings.local.json");
|
|
107
|
+
if (fs.existsSync(projectSettingsPath)) {
|
|
108
|
+
const projectSettings = JSON.parse(fs.readFileSync(projectSettingsPath, "utf8"));
|
|
109
|
+
if (projectSettings.disablePlugin === true)
|
|
110
|
+
return false;
|
|
111
|
+
const plugins = projectSettings.plugins;
|
|
112
|
+
if (plugins && plugins.forge === false)
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return true; // Non-fatal — assume enabled if cannot read settings
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// ── Function (4): Multi-plugin scanning ──────────────────────────────────────
|
|
122
|
+
/**
|
|
123
|
+
* Scans known plugin locations for all Forge installations.
|
|
124
|
+
* Ported from check-update.js scanPluginInstallations() with injectable options for testing.
|
|
125
|
+
*
|
|
126
|
+
* Returns an array of installation records with version, distribution, scope, enabled status.
|
|
127
|
+
*/
|
|
128
|
+
export function scanPluginInstallations(opts) {
|
|
129
|
+
const installations = [];
|
|
130
|
+
const homeDir = opts?.homeDir ?? os.homedir();
|
|
131
|
+
const cwd = opts?.cwd ?? process.cwd();
|
|
132
|
+
// Candidate paths — user scope (global) and project scope (local)
|
|
133
|
+
// Also scan skillforge subdirectory variant (skillforge/forge/forge)
|
|
134
|
+
const basePaths = [path.join(homeDir, ".claude", "plugins"), path.join(cwd, ".claude", "plugins")];
|
|
135
|
+
const variants = ["cache", "marketplaces"];
|
|
136
|
+
const pluginNames = ["forge/forge", "skillforge/forge/forge"];
|
|
137
|
+
const candidates = [];
|
|
138
|
+
for (const basePath of basePaths) {
|
|
139
|
+
for (const variant of variants) {
|
|
140
|
+
for (const pluginName of pluginNames) {
|
|
141
|
+
candidates.push(path.join(basePath, variant, pluginName));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
for (const candidate of candidates) {
|
|
146
|
+
try {
|
|
147
|
+
const pluginJsonPath = path.join(candidate, ".claude-plugin", "plugin.json");
|
|
148
|
+
if (!fs.existsSync(pluginJsonPath))
|
|
149
|
+
continue;
|
|
150
|
+
const manifest = JSON.parse(fs.readFileSync(pluginJsonPath, "utf8"));
|
|
151
|
+
// Determine scope: project-scope paths start with cwd/.claude (check first to avoid
|
|
152
|
+
// false positives when cwd is a subdirectory of homeDir)
|
|
153
|
+
const isProjectScope = candidate.startsWith(path.join(cwd, ".claude"));
|
|
154
|
+
const isUserScope = candidate.startsWith(path.join(homeDir, ".claude"));
|
|
155
|
+
const scope = isProjectScope ? "project" : isUserScope ? "user" : "unknown";
|
|
156
|
+
const enabled = isPluginEnabled(candidate, scope, homeDir, cwd);
|
|
157
|
+
// Avoid duplicates
|
|
158
|
+
if (installations.some((i) => i.path === candidate))
|
|
159
|
+
continue;
|
|
160
|
+
installations.push({
|
|
161
|
+
path: candidate,
|
|
162
|
+
version: manifest.version ?? "unknown",
|
|
163
|
+
distribution: detectDistribution(candidate),
|
|
164
|
+
scope,
|
|
165
|
+
enabled,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
// Non-fatal — skip broken installations silently
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return installations;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Builds a multi-plugin awareness message if multiple Forge installations are detected.
|
|
176
|
+
* Returns null if only one (or zero) installations found.
|
|
177
|
+
*/
|
|
178
|
+
export function buildMultiPluginMsg(opts) {
|
|
179
|
+
try {
|
|
180
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
181
|
+
const homeDir = opts.homeDir ?? os.homedir();
|
|
182
|
+
const allInstallations = scanPluginInstallations({ homeDir, cwd });
|
|
183
|
+
if (allInstallations.length < 2)
|
|
184
|
+
return null;
|
|
185
|
+
const activeInst = allInstallations.find((i) => i.path === opts.forgeRoot) ?? allInstallations[0];
|
|
186
|
+
if (!activeInst)
|
|
187
|
+
return null;
|
|
188
|
+
const otherInsts = allInstallations.filter((i) => i.path !== activeInst.path);
|
|
189
|
+
if (otherInsts.length === 0)
|
|
190
|
+
return null;
|
|
191
|
+
const otherDesc = otherInsts.map((i) => `${i.version} (${i.distribution}, ${i.scope})`).join(", ");
|
|
192
|
+
return `Forge: also installed: ${otherDesc}.`;
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
// ── Function (3): Distribution-switch detection + forgeRoot/forgeRef sync ─────
|
|
199
|
+
/**
|
|
200
|
+
* Syncs paths.forgeRoot and paths.forgeRef in .forge/config.json if they drift.
|
|
201
|
+
* Detects distribution switches (forge@forge ↔ forge@skillforge) and returns a
|
|
202
|
+
* notification message if the distribution changed; returns null otherwise.
|
|
203
|
+
*
|
|
204
|
+
* Also updates the project-level cache (.forge/update-check-cache.json) with
|
|
205
|
+
* current distribution, forgeRoot, and forgeRef (preserving updateStatus, pendingMigrations).
|
|
206
|
+
*
|
|
207
|
+
* Corresponds to the "Distribution + forgeRoot/forgeRef sync" section in check-update.js.
|
|
208
|
+
*/
|
|
209
|
+
export function syncForgeRootAndRef(opts) {
|
|
210
|
+
try {
|
|
211
|
+
const forgeDir = path.dirname(opts.configPath);
|
|
212
|
+
const currentDistribution = detectDistribution(opts.forgeRoot);
|
|
213
|
+
// Read current local version from the live plugin.json (faithful port of check-update.js §4.2)
|
|
214
|
+
let localVersion = "unknown";
|
|
215
|
+
try {
|
|
216
|
+
const pluginJsonPath = path.join(opts.forgeRoot, ".claude-plugin", "plugin.json");
|
|
217
|
+
const manifest = JSON.parse(fs.readFileSync(pluginJsonPath, "utf8"));
|
|
218
|
+
localVersion = manifest.version ?? "unknown";
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
// Non-fatal — version sync skipped if plugin.json unreadable
|
|
222
|
+
}
|
|
223
|
+
// Sync config.json paths.forgeRoot and paths.forgeRef
|
|
224
|
+
try {
|
|
225
|
+
const cfg = JSON.parse(fs.readFileSync(opts.configPath, "utf8"));
|
|
226
|
+
if (!cfg.paths)
|
|
227
|
+
cfg.paths = {};
|
|
228
|
+
let configChanged = false;
|
|
229
|
+
if (cfg.paths.forgeRoot !== opts.forgeRoot) {
|
|
230
|
+
cfg.paths.forgeRoot = opts.forgeRoot;
|
|
231
|
+
configChanged = true;
|
|
232
|
+
}
|
|
233
|
+
if (localVersion !== "unknown" && cfg.paths.forgeRef !== localVersion) {
|
|
234
|
+
cfg.paths.forgeRef = localVersion;
|
|
235
|
+
configChanged = true;
|
|
236
|
+
}
|
|
237
|
+
if (configChanged) {
|
|
238
|
+
fs.writeFileSync(opts.configPath, JSON.stringify(cfg, null, 2) + "\n");
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
catch {
|
|
242
|
+
// Non-fatal — config sync skipped
|
|
243
|
+
}
|
|
244
|
+
// Read project cache for distribution-switch detection
|
|
245
|
+
const projectCache = readProjectCache(forgeDir);
|
|
246
|
+
if (!projectCache)
|
|
247
|
+
return null;
|
|
248
|
+
const storedRoot = projectCache.forgeRoot;
|
|
249
|
+
const storedDist = projectCache.distribution;
|
|
250
|
+
const switched = storedRoot !== undefined && storedRoot !== opts.forgeRoot;
|
|
251
|
+
let distributionSwitchMsg = null;
|
|
252
|
+
if (switched && storedDist !== undefined && storedDist !== currentDistribution) {
|
|
253
|
+
const versionNote = projectCache.localVersion !== undefined && projectCache.localVersion !== localVersion
|
|
254
|
+
? ` Version: ${projectCache.localVersion} → ${localVersion}.`
|
|
255
|
+
: "";
|
|
256
|
+
distributionSwitchMsg =
|
|
257
|
+
`Forge: plugin distribution switched from ${storedDist} to ${currentDistribution}.${versionNote}` +
|
|
258
|
+
` paths.forgeRoot updated. Run /forge:update to verify migration state.`;
|
|
259
|
+
}
|
|
260
|
+
// Sync distribution + forgeRoot + forgeRef into the project cache whenever they drift.
|
|
261
|
+
// Preserve updateStatus, pendingReason, pendingMigrations via spread (FR-002 parity).
|
|
262
|
+
if (storedRoot !== opts.forgeRoot || storedDist !== currentDistribution) {
|
|
263
|
+
try {
|
|
264
|
+
const updated = {
|
|
265
|
+
...projectCache,
|
|
266
|
+
distribution: currentDistribution,
|
|
267
|
+
forgeRoot: opts.forgeRoot,
|
|
268
|
+
forgeRef: localVersion,
|
|
269
|
+
};
|
|
270
|
+
const cacheFile = path.join(forgeDir, "update-check-cache.json");
|
|
271
|
+
fs.writeFileSync(cacheFile, JSON.stringify(updated, null, 2) + "\n");
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
// Non-fatal
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return distributionSwitchMsg;
|
|
278
|
+
}
|
|
279
|
+
catch {
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
// ── Function (5): Pending-migration state surfacing ───────────────────────────
|
|
284
|
+
/**
|
|
285
|
+
* Surfaces a pending-migration warning if the project cache shows updateStatus === "pending".
|
|
286
|
+
* Returns the warning message string, or null if no pending state or cache absent.
|
|
287
|
+
*
|
|
288
|
+
* Corresponds to FR-002 pending-state surfacing in check-update.js.
|
|
289
|
+
*/
|
|
290
|
+
export function buildPendingMigrationMsg(configPath) {
|
|
291
|
+
try {
|
|
292
|
+
const forgeDir = path.dirname(configPath);
|
|
293
|
+
const projectCache = readProjectCache(forgeDir);
|
|
294
|
+
if (!projectCache)
|
|
295
|
+
return null;
|
|
296
|
+
if (projectCache.updateStatus !== "pending")
|
|
297
|
+
return null;
|
|
298
|
+
const pendingMigrations = Array.isArray(projectCache.pendingMigrations)
|
|
299
|
+
? projectCache.pendingMigrations.join(", ")
|
|
300
|
+
: "(unknown)";
|
|
301
|
+
return (`Forge update is incomplete — pending migration(s): ${pendingMigrations}. ` +
|
|
302
|
+
`Run /forge:update to continue or /forge:migrate to complete.`);
|
|
303
|
+
}
|
|
304
|
+
catch {
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
//# sourceMappingURL=check-update.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check-update.js","sourceRoot":"","sources":["../../../../src/extensions/forgecli/hooks/check-update.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAC9F,EAAE;AACF,iFAAiF;AACjF,oFAAoF;AACpF,2EAA2E;AAC3E,EAAE;AACF,yBAAyB;AACzB,0EAA0E;AAC1E,sGAAsG;AACtG,8DAA8D;AAC9D,8EAA8E;AAC9E,2EAA2E;AAC3E,EAAE;AACF,+BAA+B;AAC/B,oFAAoF;AACpF,gFAAgF;AAChF,iDAAiD;AACjD,mFAAmF;AACnF,+CAA+C;AAC/C,qFAAqF;AACrF,kFAAkF;AAClF,iFAAiF;AACjF,qFAAqF;AACrF,EAAE;AACF,oFAAoF;AACpF,EAAE;AACF,wFAAwF;AACxF,EAAE;AACF,aAAa;AACb,8DAA8D;AAC9D,0EAA0E;AAC1E,gFAAgF;AAEhF,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AA8BlC,iFAAiF;AAEjF;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC9C,OAAO,IAAI,CAAC,QAAQ,CAAC,0BAA0B,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,iCAAiC,CAAC;QACnG,CAAC,CAAC,kBAAkB;QACpB,CAAC,CAAC,aAAa,CAAC;AAClB,CAAC;AAED,iFAAiF;AAEjF;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAAgB;IAChD,IAAI,CAAC;QACJ,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,yBAAyB,CAAC,CAAC;QACjE,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;QAC/C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAiB,CAAC;QAC/C,OAAO,MAAM,CAAC;IACf,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,IAAI,CAAC;IACb,CAAC;AACF,CAAC;AAED,iFAAiF;AAEjF;;;;;;;;;GASG;AACH,MAAM,UAAU,sBAAsB,CAAC,UAAkB;IACxD,IAAI,CAAC;QACJ,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC;YAAE,OAAO,IAAI,CAAC;QAC5C,OAAO,CACN,6EAA6E;YAC7E,gFAAgF;YAChF,yFAAyF;YACzF,qDAAqD,CACrD,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,IAAI,CAAC;IACb,CAAC;AACF,CAAC;AAED,gFAAgF;AAEhF;;;GAGG;AACH,SAAS,eAAe,CAAC,UAAkB,EAAE,KAAa,EAAE,OAAe,EAAE,GAAW;IACvF,IAAI,CAAC;QACJ,MAAM,gBAAgB,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,EAAE,eAAe,CAAC,CAAC;QACxE,IAAI,EAAE,CAAC,UAAU,CAAC,gBAAgB,CAAC,EAAE,CAAC;YACrC,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAA4B,CAAC;YACtG,IAAI,YAAY,CAAC,aAAa,KAAK,IAAI;gBAAE,OAAO,KAAK,CAAC;YACtD,MAAM,OAAO,GAAG,YAAY,CAAC,OAA8C,CAAC;YAC5E,IAAI,OAAO,IAAI,OAAO,CAAC,KAAK,KAAK,KAAK;gBAAE,OAAO,KAAK,CAAC;QACtD,CAAC;QAED,MAAM,mBAAmB,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,EAAE,qBAAqB,CAAC,CAAC;QAC7E,IAAI,EAAE,CAAC,UAAU,CAAC,mBAAmB,CAAC,EAAE,CAAC;YACxC,MAAM,eAAe,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,mBAAmB,EAAE,MAAM,CAAC,CAG9E,CAAC;YACF,IAAI,eAAe,CAAC,aAAa,KAAK,IAAI;gBAAE,OAAO,KAAK,CAAC;YACzD,MAAM,OAAO,GAAG,eAAe,CAAC,OAA8C,CAAC;YAC/E,IAAI,OAAO,IAAI,OAAO,CAAC,KAAK,KAAK,KAAK;gBAAE,OAAO,KAAK,CAAC;QACtD,CAAC;QAED,OAAO,IAAI,CAAC;IACb,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,IAAI,CAAC,CAAC,qDAAqD;IACnE,CAAC;AACF,CAAC;AAED,gFAAgF;AAEhF;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CAAC,IAAyC;IAChF,MAAM,aAAa,GAAoB,EAAE,CAAC;IAC1C,MAAM,OAAO,GAAG,IAAI,EAAE,OAAO,IAAI,EAAE,CAAC,OAAO,EAAE,CAAC;IAC9C,MAAM,GAAG,GAAG,IAAI,EAAE,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IAEvC,kEAAkE;IAClE,qEAAqE;IACrE,MAAM,SAAS,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,EAAE,SAAS,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC;IACnG,MAAM,QAAQ,GAAG,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;IAC3C,MAAM,WAAW,GAAG,CAAC,aAAa,EAAE,wBAAwB,CAAC,CAAC;IAE9D,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;QAClC,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;YAChC,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;gBACtC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC;YAC3D,CAAC;QACF,CAAC;IACF,CAAC;IAED,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACpC,IAAI,CAAC;YACJ,MAAM,cAAc,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,gBAAgB,EAAE,aAAa,CAAC,CAAC;YAC7E,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,cAAc,CAAC;gBAAE,SAAS;YAE7C,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,cAAc,EAAE,MAAM,CAAC,CAAyB,CAAC;YAC7F,oFAAoF;YACpF,yDAAyD;YACzD,MAAM,cAAc,GAAG,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC,CAAC;YACvE,MAAM,WAAW,GAAG,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC;YACxE,MAAM,KAAK,GAAmC,cAAc,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;YAC5G,MAAM,OAAO,GAAG,eAAe,CAAC,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;YAEhE,mBAAmB;YACnB,IAAI,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS,CAAC;gBAAE,SAAS;YAE9D,aAAa,CAAC,IAAI,CAAC;gBAClB,IAAI,EAAE,SAAS;gBACf,OAAO,EAAE,QAAQ,CAAC,OAAO,IAAI,SAAS;gBACtC,YAAY,EAAE,kBAAkB,CAAC,SAAS,CAAC;gBAC3C,KAAK;gBACL,OAAO;aACP,CAAC,CAAC;QACJ,CAAC;QAAC,MAAM,CAAC;YACR,iDAAiD;QAClD,CAAC;IACF,CAAC;IAED,OAAO,aAAa,CAAC;AACtB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAwB;IAC3D,IAAI,CAAC;QACJ,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;QACtC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC,OAAO,EAAE,CAAC;QAC7C,MAAM,gBAAgB,GAAG,uBAAuB,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC;QAEnE,IAAI,gBAAgB,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC;QAE7C,MAAM,UAAU,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,SAAS,CAAC,IAAI,gBAAgB,CAAC,CAAC,CAAC,CAAC;QAClG,IAAI,CAAC,UAAU;YAAE,OAAO,IAAI,CAAC;QAC7B,MAAM,UAAU,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,UAAU,CAAC,IAAI,CAAC,CAAC;QAC9E,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QAEzC,MAAM,SAAS,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,YAAY,KAAK,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnG,OAAO,0BAA0B,SAAS,GAAG,CAAC;IAC/C,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,IAAI,CAAC;IACb,CAAC;AACF,CAAC;AAED,iFAAiF;AAEjF;;;;;;;;;GASG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAwB;IAC3D,IAAI,CAAC;QACJ,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC/C,MAAM,mBAAmB,GAAG,kBAAkB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAE/D,+FAA+F;QAC/F,IAAI,YAAY,GAAG,SAAS,CAAC;QAC7B,IAAI,CAAC;YACJ,MAAM,cAAc,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,gBAAgB,EAAE,aAAa,CAAC,CAAC;YAClF,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,cAAc,EAAE,MAAM,CAAC,CAAyB,CAAC;YAC7F,YAAY,GAAG,QAAQ,CAAC,OAAO,IAAI,SAAS,CAAC;QAC9C,CAAC;QAAC,MAAM,CAAC;YACR,6DAA6D;QAC9D,CAAC;QAED,sDAAsD;QACtD,IAAI,CAAC;YACJ,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,CAE9D,CAAC;YACF,IAAI,CAAC,GAAG,CAAC,KAAK;gBAAE,GAAG,CAAC,KAAK,GAAG,EAAE,CAAC;YAC/B,IAAI,aAAa,GAAG,KAAK,CAAC;YAC1B,IAAI,GAAG,CAAC,KAAK,CAAC,SAAS,KAAK,IAAI,CAAC,SAAS,EAAE,CAAC;gBAC5C,GAAG,CAAC,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;gBACrC,aAAa,GAAG,IAAI,CAAC;YACtB,CAAC;YACD,IAAI,YAAY,KAAK,SAAS,IAAI,GAAG,CAAC,KAAK,CAAC,QAAQ,KAAK,YAAY,EAAE,CAAC;gBACvE,GAAG,CAAC,KAAK,CAAC,QAAQ,GAAG,YAAY,CAAC;gBAClC,aAAa,GAAG,IAAI,CAAC;YACtB,CAAC;YACD,IAAI,aAAa,EAAE,CAAC;gBACnB,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;YACxE,CAAC;QACF,CAAC;QAAC,MAAM,CAAC;YACR,kCAAkC;QACnC,CAAC;QAED,uDAAuD;QACvD,MAAM,YAAY,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAC;QAChD,IAAI,CAAC,YAAY;YAAE,OAAO,IAAI,CAAC;QAE/B,MAAM,UAAU,GAAG,YAAY,CAAC,SAAS,CAAC;QAC1C,MAAM,UAAU,GAAG,YAAY,CAAC,YAAY,CAAC;QAC7C,MAAM,QAAQ,GAAG,UAAU,KAAK,SAAS,IAAI,UAAU,KAAK,IAAI,CAAC,SAAS,CAAC;QAE3E,IAAI,qBAAqB,GAAkB,IAAI,CAAC;QAEhD,IAAI,QAAQ,IAAI,UAAU,KAAK,SAAS,IAAI,UAAU,KAAK,mBAAmB,EAAE,CAAC;YAChF,MAAM,WAAW,GAChB,YAAY,CAAC,YAAY,KAAK,SAAS,IAAI,YAAY,CAAC,YAAY,KAAK,YAAY;gBACpF,CAAC,CAAC,aAAa,YAAY,CAAC,YAAY,MAAM,YAAY,GAAG;gBAC7D,CAAC,CAAC,EAAE,CAAC;YACP,qBAAqB;gBACpB,4CAA4C,UAAU,OAAO,mBAAmB,IAAI,WAAW,EAAE;oBACjG,wEAAwE,CAAC;QAC3E,CAAC;QAED,uFAAuF;QACvF,sFAAsF;QACtF,IAAI,UAAU,KAAK,IAAI,CAAC,SAAS,IAAI,UAAU,KAAK,mBAAmB,EAAE,CAAC;YACzE,IAAI,CAAC;gBACJ,MAAM,OAAO,GAAiB;oBAC7B,GAAG,YAAY;oBACf,YAAY,EAAE,mBAAmB;oBACjC,SAAS,EAAE,IAAI,CAAC,SAAS;oBACzB,QAAQ,EAAE,YAAY;iBACtB,CAAC;gBACF,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,yBAAyB,CAAC,CAAC;gBACjE,EAAE,CAAC,aAAa,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;YACtE,CAAC;YAAC,MAAM,CAAC;gBACR,YAAY;YACb,CAAC;QACF,CAAC;QAED,OAAO,qBAAqB,CAAC;IAC9B,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,IAAI,CAAC;IACb,CAAC;AACF,CAAC;AAED,iFAAiF;AAEjF;;;;;GAKG;AACH,MAAM,UAAU,wBAAwB,CAAC,UAAkB;IAC1D,IAAI,CAAC;QACJ,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QAC1C,MAAM,YAAY,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAC;QAChD,IAAI,CAAC,YAAY;YAAE,OAAO,IAAI,CAAC;QAC/B,IAAI,YAAY,CAAC,YAAY,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC;QAEzD,MAAM,iBAAiB,GAAG,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,iBAAiB,CAAC;YACtE,CAAC,CAAC,YAAY,CAAC,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC;YAC3C,CAAC,CAAC,WAAW,CAAC;QAEf,OAAO,CACN,sDAAsD,iBAAiB,IAAI;YAC3E,8DAA8D,CAC9D,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,IAAI,CAAC;IACb,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** A single pattern entry in the permission registry. */
|
|
2
|
+
interface PermissionPattern {
|
|
3
|
+
/** Regex tested against the tool input string. */
|
|
4
|
+
pattern: RegExp;
|
|
5
|
+
/**
|
|
6
|
+
* Human-readable rule string — kept for audit-log display only.
|
|
7
|
+
* NOT persisted: pi has no PermissionRequest event.
|
|
8
|
+
*/
|
|
9
|
+
rule: string;
|
|
10
|
+
}
|
|
11
|
+
/** Bash command patterns that are auto-allowed. */
|
|
12
|
+
export declare const BASH_PATTERNS: PermissionPattern[];
|
|
13
|
+
/** Write tool path patterns that are auto-allowed. */
|
|
14
|
+
export declare const WRITE_PATTERNS: PermissionPattern[];
|
|
15
|
+
/** Edit tool path patterns that are auto-allowed. */
|
|
16
|
+
export declare const EDIT_PATTERNS: PermissionPattern[];
|
|
17
|
+
/**
|
|
18
|
+
* Check whether a pi tool_call matches a known Forge auto-allow pattern.
|
|
19
|
+
*
|
|
20
|
+
* @param toolName The pi tool name ("bash", "write", "edit", etc.)
|
|
21
|
+
* @param toolInput The tool's input object (toolCallEvent.input)
|
|
22
|
+
* @returns The matched rule string for audit logging, or null if no match.
|
|
23
|
+
* A non-null return is the "silently allow" signal — the caller
|
|
24
|
+
* should return undefined from the tool_call handler (no block).
|
|
25
|
+
*
|
|
26
|
+
* Input fields tested per tool type (mirrors plugin matchTool):
|
|
27
|
+
* bash → toolInput.command (string)
|
|
28
|
+
* write → toolInput.path (string; pi uses `path`, not `file_path`)
|
|
29
|
+
* edit → toolInput.path (string)
|
|
30
|
+
*/
|
|
31
|
+
export declare function matchForgePermission(toolName: string, toolInput: Record<string, unknown>): string | null;
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// Forge permission pattern registry — FORGE-S23-T04.
|
|
2
|
+
//
|
|
3
|
+
// Port of forge/forge/hooks/forge-permissions.js pattern-match auto-allow logic.
|
|
4
|
+
// Pi has no PermissionRequest event, so "auto-allow" means: when the tool_call
|
|
5
|
+
// matches a known Forge pattern, silently return undefined (no block) from the
|
|
6
|
+
// tool_call handler, bypassing the two-layer-guard boundary check.
|
|
7
|
+
//
|
|
8
|
+
// Key differences from the plugin:
|
|
9
|
+
// - No updatedPermissions / localSettings.json persistence: pi has no
|
|
10
|
+
// PermissionRequest event or persistent allow-rule protocol. The rule
|
|
11
|
+
// string is kept for audit-log display only.
|
|
12
|
+
// - WEBFETCH_PATTERNS omitted: pi's @earendil-works/pi-coding-agent has no
|
|
13
|
+
// WebFetchToolCallEvent type in its exported union. Adding dead code that
|
|
14
|
+
// could never fire would be misleading.
|
|
15
|
+
// - MultiEdit patterns omitted: pi has no MultiEdit event.
|
|
16
|
+
// - node -e / node -p remain excluded: arbitrary code execution must not
|
|
17
|
+
// be auto-approved. (Security decision from the original plugin, preserved.)
|
|
18
|
+
//
|
|
19
|
+
// File placement rationale: task prompt suggested "into two-layer-guard.ts",
|
|
20
|
+
// but that file has a tight single-responsibility scope. The T02 (write-guard.ts)
|
|
21
|
+
// and T03 (triage-error.ts) siblings establish the pattern: one concern per file.
|
|
22
|
+
// This file follows that convention per forge-cli-engineer Iron Law 8 (prefer
|
|
23
|
+
// architectural symmetry over literal prompt when prior art exists in the codebase).
|
|
24
|
+
// ── Pattern registries — ported verbatim from forge-permissions.js ──────────
|
|
25
|
+
/** Bash command patterns that are auto-allowed. */
|
|
26
|
+
export const BASH_PATTERNS = [
|
|
27
|
+
// Node tool invocations — covers $FORGE_ROOT/tools/*.cjs and $CLAUDE_PLUGIN_ROOT
|
|
28
|
+
{ pattern: /^node\s+.*\/tools\/[\w-]+\.(cjs|js)\b/, rule: "node ~/.claude/plugins/cache/forge/forge/*/tools/*" },
|
|
29
|
+
// NOTE: node -e and node -p are intentionally excluded — arbitrary code
|
|
30
|
+
// execution must not be auto-approved. Forge workflows use node .../tools/*.cjs
|
|
31
|
+
// for tool invocations; inline node -e/p requires explicit user approval.
|
|
32
|
+
// Shell commands used by Forge workflows
|
|
33
|
+
{ pattern: /^mkdir\s+-p\s+/, rule: "mkdir -p .forge/*" },
|
|
34
|
+
{ pattern: /^mkdir\s+-p\s+\S+/, rule: "mkdir -p .forge/*" },
|
|
35
|
+
{ pattern: /^cp\s+/, rule: "cp */schemas/*.schema.json .forge/schemas/" },
|
|
36
|
+
{ pattern: /^ls\s+/, rule: "ls *" },
|
|
37
|
+
{ pattern: /^cat\s+/, rule: "cat .forge/*" },
|
|
38
|
+
{ pattern: /^date\s+-u\s+/, rule: "date -u *" },
|
|
39
|
+
{ pattern: /^date\s+/, rule: "date -u *" },
|
|
40
|
+
{ pattern: /^jq\s+/, rule: "jq *" },
|
|
41
|
+
{ pattern: /^touch\s+/, rule: "touch .forge/*" },
|
|
42
|
+
{ pattern: /^uname\s+/, rule: "uname *" },
|
|
43
|
+
{ pattern: /^rm\s+\.forge/, rule: "rm .forge/*" },
|
|
44
|
+
{ pattern: /^rm\s+-rf\s+\.forge/, rule: "rm -rf .forge/*" },
|
|
45
|
+
{ pattern: /^rmdir\s+/, rule: "rmdir .forge/*" },
|
|
46
|
+
{ pattern: /^gh\s+auth\s+/, rule: "gh auth status *" },
|
|
47
|
+
{ pattern: /^gh\s+issue\s+/, rule: "gh issue create *" },
|
|
48
|
+
// git read-only commands (already auto-approved by Claude Code, but belt-and-suspenders)
|
|
49
|
+
{ pattern: /^git\s+status\b/, rule: "git status *" },
|
|
50
|
+
{ pattern: /^git\s+log\b/, rule: "git log *" },
|
|
51
|
+
{ pattern: /^git\s+diff\b/, rule: "git diff *" },
|
|
52
|
+
{ pattern: /^git\s+add\s+/, rule: "git add *" },
|
|
53
|
+
{ pattern: /^git\s+commit\s+-m\s+/, rule: "git commit -m *" },
|
|
54
|
+
{ pattern: /^git\s+push\b/, rule: "git push *" },
|
|
55
|
+
{ pattern: /^git\s+checkout\s+/, rule: "git checkout *" },
|
|
56
|
+
{ pattern: /^git\s+branch\s+/, rule: "git branch *" },
|
|
57
|
+
{ pattern: /^git\s+stash\b/, rule: "git stash *" },
|
|
58
|
+
{ pattern: /^git\s+worktree\s+/, rule: "git worktree *" },
|
|
59
|
+
];
|
|
60
|
+
/** Write tool path patterns that are auto-allowed. */
|
|
61
|
+
export const WRITE_PATTERNS = [
|
|
62
|
+
{ pattern: /^\.forge\//, rule: ".forge/**" },
|
|
63
|
+
{ pattern: /^\.claude\/commands\//, rule: ".claude/commands/**" },
|
|
64
|
+
{ pattern: /^engineering\//, rule: "engineering/**" },
|
|
65
|
+
{ pattern: /^CLAUDE\.md$/i, rule: "CLAUDE.md" },
|
|
66
|
+
{ pattern: /^AGENTS\.md$/i, rule: "AGENTS.md" },
|
|
67
|
+
{ pattern: /^\.gitignore$/, rule: ".gitignore" },
|
|
68
|
+
];
|
|
69
|
+
/** Edit tool path patterns that are auto-allowed. */
|
|
70
|
+
export const EDIT_PATTERNS = [
|
|
71
|
+
{ pattern: /^\.forge\//, rule: ".forge/**" },
|
|
72
|
+
{ pattern: /^\.claude\/commands\//, rule: ".claude/commands/**" },
|
|
73
|
+
{ pattern: /^engineering\//, rule: "engineering/**" },
|
|
74
|
+
{ pattern: /^CLAUDE\.md$/i, rule: "CLAUDE.md" },
|
|
75
|
+
{ pattern: /^AGENTS\.md$/i, rule: "AGENTS.md" },
|
|
76
|
+
];
|
|
77
|
+
// Map from pi tool name to its pattern list.
|
|
78
|
+
// NOTE: "multiedit" is omitted — pi has no MultiEdit event in its tool_call union.
|
|
79
|
+
// NOTE: "webfetch" is omitted — pi has no WebFetchToolCallEvent type.
|
|
80
|
+
const PATTERN_MAP = {
|
|
81
|
+
bash: BASH_PATTERNS,
|
|
82
|
+
write: WRITE_PATTERNS,
|
|
83
|
+
edit: EDIT_PATTERNS,
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Check whether a pi tool_call matches a known Forge auto-allow pattern.
|
|
87
|
+
*
|
|
88
|
+
* @param toolName The pi tool name ("bash", "write", "edit", etc.)
|
|
89
|
+
* @param toolInput The tool's input object (toolCallEvent.input)
|
|
90
|
+
* @returns The matched rule string for audit logging, or null if no match.
|
|
91
|
+
* A non-null return is the "silently allow" signal — the caller
|
|
92
|
+
* should return undefined from the tool_call handler (no block).
|
|
93
|
+
*
|
|
94
|
+
* Input fields tested per tool type (mirrors plugin matchTool):
|
|
95
|
+
* bash → toolInput.command (string)
|
|
96
|
+
* write → toolInput.path (string; pi uses `path`, not `file_path`)
|
|
97
|
+
* edit → toolInput.path (string)
|
|
98
|
+
*/
|
|
99
|
+
export function matchForgePermission(toolName, toolInput) {
|
|
100
|
+
const patterns = PATTERN_MAP[toolName];
|
|
101
|
+
if (!patterns)
|
|
102
|
+
return null;
|
|
103
|
+
let input;
|
|
104
|
+
if (toolName === "bash") {
|
|
105
|
+
input = typeof toolInput.command === "string" ? toolInput.command : "";
|
|
106
|
+
}
|
|
107
|
+
else if (toolName === "write" || toolName === "edit") {
|
|
108
|
+
input = typeof toolInput.path === "string" ? toolInput.path : "";
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
for (const { pattern, rule } of patterns) {
|
|
114
|
+
if (pattern.test(input))
|
|
115
|
+
return rule;
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=forge-permissions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"forge-permissions.js","sourceRoot":"","sources":["../../../../src/extensions/forgecli/hooks/forge-permissions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regex patterns that identify Forge-related Bash commands.
|
|
3
|
+
* Ported verbatim from forge/forge/hooks/triage-error.js lines 10–20.
|
|
4
|
+
* Exported as `readonly` so tests can verify count and identity.
|
|
5
|
+
*/
|
|
6
|
+
export declare const FORGE_PATTERNS: readonly RegExp[];
|
|
7
|
+
/**
|
|
8
|
+
* Returns true if the command matches any Forge-related pattern.
|
|
9
|
+
* Mirrors `isForgeRelated()` from triage-error.js.
|
|
10
|
+
*/
|
|
11
|
+
export declare function isForgeRelated(command: string): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Build the ctx.ui.notify message for a failed Forge-related command.
|
|
14
|
+
*
|
|
15
|
+
* Format (condensed for notification surface):
|
|
16
|
+
* FORGE_ERROR_TRIAGE: A Forge command just failed. [First error line: "...". ]
|
|
17
|
+
* Would you like to file a bug? Run /forge:report-bug
|
|
18
|
+
*
|
|
19
|
+
* @param command The original bash command string (unused in message body; kept for future use).
|
|
20
|
+
* @param outputSnippet First few lines of tool output, already joined to a single string.
|
|
21
|
+
* Empty string if no output is available.
|
|
22
|
+
*/
|
|
23
|
+
export declare function buildTriageMessage(_command: string, outputSnippet: string): string;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// hooks/triage-error.ts — Post-Bash-failure Forge error triage (FORGE-S23-T03).
|
|
2
|
+
//
|
|
3
|
+
// Port of forge/forge/hooks/triage-error.js (71 lines) for the pi extension context.
|
|
4
|
+
//
|
|
5
|
+
// When a Bash tool call exits non-zero and the command matches a Forge-related
|
|
6
|
+
// pattern, the hook-dispatcher calls isForgeRelated() and, on a match, uses
|
|
7
|
+
// buildTriageMessage() to build a ctx.ui.notify message suggesting the user
|
|
8
|
+
// run /forge:report-bug.
|
|
9
|
+
//
|
|
10
|
+
// This module is a pure helper — no pi imports, no side effects. All logic is
|
|
11
|
+
// exported so tests can exercise it in isolation.
|
|
12
|
+
//
|
|
13
|
+
// Plugin parity reference: forge/forge/hooks/triage-error.js (read-only — do NOT edit).
|
|
14
|
+
//
|
|
15
|
+
// Iron Laws:
|
|
16
|
+
// IL1 — code only under forge-cli/src/extensions/forgecli/.
|
|
17
|
+
// IL6 — no shell-string interpolation; no spawn calls.
|
|
18
|
+
// ── Pattern set (ported verbatim from triage-error.js:10-20) ─────────────────
|
|
19
|
+
/**
|
|
20
|
+
* Regex patterns that identify Forge-related Bash commands.
|
|
21
|
+
* Ported verbatim from forge/forge/hooks/triage-error.js lines 10–20.
|
|
22
|
+
* Exported as `readonly` so tests can verify count and identity.
|
|
23
|
+
*/
|
|
24
|
+
export const FORGE_PATTERNS = [
|
|
25
|
+
/manage-config/,
|
|
26
|
+
/\.forge\//,
|
|
27
|
+
/CLAUDE_PLUGIN_ROOT/,
|
|
28
|
+
/FORGE_ROOT/,
|
|
29
|
+
/MANAGE_CONFIG/,
|
|
30
|
+
/engineering\/tools\//,
|
|
31
|
+
/forge:init/,
|
|
32
|
+
/forge:health/,
|
|
33
|
+
/forge:regenerate/,
|
|
34
|
+
/forge:update/,
|
|
35
|
+
/forge:add-pipeline/,
|
|
36
|
+
];
|
|
37
|
+
// ── Pure helpers ──────────────────────────────────────────────────────────────
|
|
38
|
+
/**
|
|
39
|
+
* Returns true if the command matches any Forge-related pattern.
|
|
40
|
+
* Mirrors `isForgeRelated()` from triage-error.js.
|
|
41
|
+
*/
|
|
42
|
+
export function isForgeRelated(command) {
|
|
43
|
+
return FORGE_PATTERNS.some((p) => p.test(command));
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Build the ctx.ui.notify message for a failed Forge-related command.
|
|
47
|
+
*
|
|
48
|
+
* Format (condensed for notification surface):
|
|
49
|
+
* FORGE_ERROR_TRIAGE: A Forge command just failed. [First error line: "...". ]
|
|
50
|
+
* Would you like to file a bug? Run /forge:report-bug
|
|
51
|
+
*
|
|
52
|
+
* @param command The original bash command string (unused in message body; kept for future use).
|
|
53
|
+
* @param outputSnippet First few lines of tool output, already joined to a single string.
|
|
54
|
+
* Empty string if no output is available.
|
|
55
|
+
*/
|
|
56
|
+
export function buildTriageMessage(_command, outputSnippet) {
|
|
57
|
+
const snippetPart = outputSnippet ? `First error line: "${outputSnippet}". ` : "";
|
|
58
|
+
return (`FORGE_ERROR_TRIAGE: A Forge command just failed. ` +
|
|
59
|
+
snippetPart +
|
|
60
|
+
`Would you like to file a bug? Run /forge:report-bug`);
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=triage-error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"triage-error.js","sourceRoot":"","sources":["../../../../src/extensions/forgecli/hooks/triage-error.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,EAAE;AACF,qFAAqF;AACrF,EAAE;AACF,+EAA+E;AAC/E,4EAA4E;AAC5E,4EAA4E;AAC5E,yBAAyB;AACzB,EAAE;AACF,8EAA8E;AAC9E,kDAAkD;AAClD,EAAE;AACF,wFAAwF;AACxF,EAAE;AACF,aAAa;AACb,8DAA8D;AAC9D,yDAAyD;AAEzD,gFAAgF;AAEhF;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GAAsB;IAChD,eAAe;IACf,WAAW;IACX,oBAAoB;IACpB,YAAY;IACZ,eAAe;IACf,sBAAsB;IACtB,YAAY;IACZ,cAAc;IACd,kBAAkB;IAClB,cAAc;IACd,oBAAoB;CACX,CAAC;AAEX,iFAAiF;AAEjF;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,OAAe;IAC7C,OAAO,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;AACpD,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,kBAAkB,CAAC,QAAgB,EAAE,aAAqB;IACzE,MAAM,WAAW,GAAG,aAAa,CAAC,CAAC,CAAC,sBAAsB,aAAa,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IAClF,OAAO,CACN,mDAAmD;QACnD,WAAW;QACX,qDAAqD,CACrD,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
interface RegistryEntry {
|
|
2
|
+
pattern: RegExp;
|
|
3
|
+
schema: string;
|
|
4
|
+
kind: string;
|
|
5
|
+
}
|
|
6
|
+
export declare const WRITE_REGISTRY: RegistryEntry[];
|
|
7
|
+
export declare function matchWriteRegistry(absPath: string): RegistryEntry | null;
|
|
8
|
+
export declare function applyPiEdits(filePath: string, edits: Array<{
|
|
9
|
+
oldText: string;
|
|
10
|
+
newText: string;
|
|
11
|
+
}>): string;
|
|
12
|
+
export interface WriteGuardResult {
|
|
13
|
+
block: boolean;
|
|
14
|
+
reason?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Check whether a Write or Edit targeting `filePath` with `contents` satisfies
|
|
18
|
+
* the Forge schema for that path.
|
|
19
|
+
*
|
|
20
|
+
* @param filePath Absolute path being written (from event.input.path, resolved).
|
|
21
|
+
* @param contents Post-write/post-edit file contents as a string.
|
|
22
|
+
* @param forgeRoot Absolute path to the Forge plugin root.
|
|
23
|
+
* @returns { block: false } if allowed; { block: true, reason } if blocked.
|
|
24
|
+
*
|
|
25
|
+
* Fail-open: returns { block: false } on any internal error.
|
|
26
|
+
*/
|
|
27
|
+
export declare function checkWriteGuard(filePath: string, contents: string, forgeRoot: string): WriteGuardResult;
|
|
28
|
+
export {};
|