@shrkcrft/inspector 0.1.0-alpha.2 → 0.1.0-alpha.20
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/dist/agent-brief.d.ts.map +1 -1
- package/dist/agent-brief.js +59 -10
- package/dist/agent-contract-gate.d.ts.map +1 -1
- package/dist/agent-contract-gate.js +25 -2
- package/dist/agent-instructions.d.ts.map +1 -1
- package/dist/agent-instructions.js +11 -0
- package/dist/agent-task-prep.d.ts.map +1 -1
- package/dist/agent-task-prep.js +1 -3
- package/dist/ai-readiness.d.ts +84 -9
- package/dist/ai-readiness.d.ts.map +1 -1
- package/dist/ai-readiness.js +181 -35
- package/dist/apply-dispatch-trace.d.ts +1 -2
- package/dist/apply-dispatch-trace.d.ts.map +1 -1
- package/dist/apply-dispatch-trace.js +0 -9
- package/dist/area-explore.d.ts.map +1 -1
- package/dist/area-explore.js +4 -6
- package/dist/area-map.d.ts +0 -5
- package/dist/area-map.d.ts.map +1 -1
- package/dist/area-map.js +0 -10
- package/dist/changed-preflight.d.ts +7 -0
- package/dist/changed-preflight.d.ts.map +1 -1
- package/dist/changed-preflight.js +56 -9
- package/dist/changes-summary.d.ts.map +1 -1
- package/dist/changes-summary.js +10 -1
- package/dist/check-guardrail-globs.d.ts +16 -0
- package/dist/check-guardrail-globs.d.ts.map +1 -0
- package/dist/check-guardrail-globs.js +38 -0
- package/dist/code-intelligence-doctor.d.ts +21 -0
- package/dist/code-intelligence-doctor.d.ts.map +1 -0
- package/dist/code-intelligence-doctor.js +985 -0
- package/dist/command-recommender.d.ts.map +1 -1
- package/dist/command-recommender.js +23 -0
- package/dist/compliance-profiles.js +1 -1
- package/dist/construct-adoption-diff.d.ts.map +1 -1
- package/dist/construct-adoption-diff.js +2 -1
- package/dist/construct-adoption.d.ts.map +1 -1
- package/dist/construct-adoption.js +10 -11
- package/dist/construct-inference.d.ts.map +1 -1
- package/dist/construct-inference.js +5 -2
- package/dist/construct-registry.d.ts.map +1 -1
- package/dist/construct-registry.js +2 -10
- package/dist/contract-file-rule.d.ts +8 -0
- package/dist/contract-file-rule.d.ts.map +1 -1
- package/dist/contract-file-rule.js +8 -3
- package/dist/contract-template-registry.d.ts.map +1 -1
- package/dist/contract-template-registry.js +2 -10
- package/dist/contradictions.d.ts +8 -1
- package/dist/contradictions.d.ts.map +1 -1
- package/dist/contradictions.js +37 -35
- package/dist/convention-registry.d.ts.map +1 -1
- package/dist/convention-registry.js +2 -10
- package/dist/coverage-report.d.ts.map +1 -1
- package/dist/coverage-report.js +14 -1
- package/dist/dashboard/dashboard-knowledge.d.ts +8 -0
- package/dist/dashboard/dashboard-knowledge.d.ts.map +1 -0
- package/dist/dashboard/dashboard-knowledge.js +259 -0
- package/dist/decision-records.d.ts.map +1 -1
- package/dist/decision-records.js +5 -10
- package/dist/delegate-catalog.d.ts +45 -0
- package/dist/delegate-catalog.d.ts.map +1 -0
- package/dist/delegate-catalog.js +50 -0
- package/dist/delegate-doctor.d.ts +15 -0
- package/dist/delegate-doctor.d.ts.map +1 -0
- package/dist/delegate-doctor.js +36 -0
- package/dist/delegate-pack-recipes.d.ts +29 -0
- package/dist/delegate-pack-recipes.d.ts.map +1 -0
- package/dist/delegate-pack-recipes.js +77 -0
- package/dist/demo-script.d.ts +0 -1
- package/dist/demo-script.d.ts.map +1 -1
- package/dist/demo-script.js +0 -43
- package/dist/docs-check.js +1 -1
- package/dist/drift-baseline.d.ts.map +1 -1
- package/dist/drift-baseline.js +5 -2
- package/dist/feedback-ingestion.d.ts.map +1 -1
- package/dist/feedback-ingestion.js +2 -16
- package/dist/git-helpers.d.ts +15 -0
- package/dist/git-helpers.d.ts.map +1 -1
- package/dist/git-helpers.js +51 -4
- package/dist/helper-registry.d.ts +27 -54
- package/dist/helper-registry.d.ts.map +1 -1
- package/dist/helper-registry.js +16 -517
- package/dist/impact-analysis.d.ts.map +1 -1
- package/dist/impact-analysis.js +14 -7
- package/dist/index.d.ts +8 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -2
- package/dist/ingest-drafts.js +8 -4
- package/dist/migration-profile-registry.d.ts.map +1 -1
- package/dist/migration-profile-registry.js +2 -10
- package/dist/monorepo-onboarding.js +2 -2
- package/dist/onboarding-report.d.ts.map +1 -1
- package/dist/onboarding-report.js +5 -1
- package/dist/onboarding.d.ts +1 -1
- package/dist/onboarding.d.ts.map +1 -1
- package/dist/onboarding.js +9 -66
- package/dist/ownership.js +2 -10
- package/dist/pack-contributions-inventory.d.ts +0 -1
- package/dist/pack-contributions-inventory.d.ts.map +1 -1
- package/dist/pack-contributions-inventory.js +17 -29
- package/dist/pack-helper-registry.d.ts.map +1 -1
- package/dist/pack-helper-registry.js +2 -10
- package/dist/pack-release-check.d.ts.map +1 -1
- package/dist/pack-release-check.js +4 -11
- package/dist/pack-signature-status.d.ts.map +1 -1
- package/dist/pack-signature-status.js +18 -2
- package/dist/pack-test-runner.js +2 -10
- package/dist/plan-review.d.ts.map +1 -1
- package/dist/plan-review.js +5 -10
- package/dist/plan-simulation.d.ts +13 -0
- package/dist/plan-simulation.d.ts.map +1 -1
- package/dist/plan-simulation.js +4 -21
- package/dist/playbook-registry.d.ts.map +1 -1
- package/dist/playbook-registry.js +2 -10
- package/dist/policy-engine.d.ts.map +1 -1
- package/dist/policy-engine.js +3 -11
- package/dist/policy-test.js +3 -11
- package/dist/profile-registry.d.ts +0 -1
- package/dist/profile-registry.d.ts.map +1 -1
- package/dist/profile-registry.js +4 -32
- package/dist/propose-knowledge.d.ts +15 -0
- package/dist/propose-knowledge.d.ts.map +1 -1
- package/dist/propose-knowledge.js +37 -4
- package/dist/quality-baseline.d.ts.map +1 -1
- package/dist/quality-baseline.js +3 -1
- package/dist/ranker-explainability.d.ts.map +1 -1
- package/dist/ranker-explainability.js +3 -9
- package/dist/registration-hint-registry.d.ts.map +1 -1
- package/dist/registration-hint-registry.js +2 -10
- package/dist/registry-lifecycle.d.ts +6 -0
- package/dist/registry-lifecycle.d.ts.map +1 -1
- package/dist/registry-lifecycle.js +137 -10
- package/dist/release-readiness.js +3 -3
- package/dist/repo-memory.d.ts.map +1 -1
- package/dist/repo-memory.js +3 -1
- package/dist/reposet.js +1 -1
- package/dist/repository-intelligence.d.ts.map +1 -1
- package/dist/repository-intelligence.js +7 -2
- package/dist/repository-knowledge-model.d.ts +1 -1
- package/dist/repository-knowledge-model.d.ts.map +1 -1
- package/dist/repository-stats.d.ts.map +1 -1
- package/dist/repository-stats.js +3 -1
- package/dist/resolve-verification-commands.d.ts +26 -0
- package/dist/resolve-verification-commands.d.ts.map +1 -0
- package/dist/resolve-verification-commands.js +55 -0
- package/dist/review-packet.d.ts.map +1 -1
- package/dist/review-packet.js +14 -17
- package/dist/rule-drift.d.ts.map +1 -1
- package/dist/rule-drift.js +24 -9
- package/dist/rule-scaffold.d.ts.map +1 -1
- package/dist/rule-scaffold.js +12 -4
- package/dist/scaffold-patterns.js +2 -10
- package/dist/search-tuning-registry.d.ts.map +1 -1
- package/dist/search-tuning-registry.js +2 -10
- package/dist/self-config-doctor-v2.d.ts +1 -1
- package/dist/self-config-doctor-v2.d.ts.map +1 -1
- package/dist/self-config-doctor-v2.js +6 -10
- package/dist/self-config-doctor.d.ts.map +1 -1
- package/dist/self-config-doctor.js +7 -13
- package/dist/sharkcraft-inspector.d.ts +14 -0
- package/dist/sharkcraft-inspector.d.ts.map +1 -1
- package/dist/sharkcraft-inspector.js +103 -1
- package/dist/start-here.d.ts +2 -2
- package/dist/start-here.d.ts.map +1 -1
- package/dist/start-here.js +16 -1
- package/dist/synthesize-from-onboarding.d.ts +68 -0
- package/dist/synthesize-from-onboarding.d.ts.map +1 -0
- package/dist/synthesize-from-onboarding.js +508 -0
- package/dist/task-packet.d.ts +13 -0
- package/dist/task-packet.d.ts.map +1 -1
- package/dist/task-packet.js +59 -6
- package/dist/task-ranker.d.ts.map +1 -1
- package/dist/task-ranker.js +1 -31
- package/dist/task-routing-hint-registry.d.ts.map +1 -1
- package/dist/task-routing-hint-registry.js +2 -10
- package/dist/template-drift.d.ts +7 -0
- package/dist/template-drift.d.ts.map +1 -1
- package/dist/template-drift.js +14 -6
- package/dist/test-impact.d.ts.map +1 -1
- package/dist/test-impact.js +5 -2
- package/dist/test-runner.d.ts.map +1 -1
- package/dist/test-runner.js +12 -17
- package/dist/universal-search.d.ts +0 -1
- package/dist/universal-search.d.ts.map +1 -1
- package/dist/universal-search.js +0 -12
- package/dist/why-file.js +66 -22
- package/package.json +18 -18
- package/dist/plugin-lifecycle-profile-registry.d.ts +0 -52
- package/dist/plugin-lifecycle-profile-registry.d.ts.map +0 -1
- package/dist/plugin-lifecycle-profile-registry.js +0 -202
- package/dist/plugin-lifecycle.d.ts +0 -132
- package/dist/plugin-lifecycle.d.ts.map +0 -1
- package/dist/plugin-lifecycle.js +0 -477
package/dist/plugin-lifecycle.js
DELETED
|
@@ -1,477 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Plan-only plugin lifecycle helpers driven by a resolved profile.
|
|
3
|
-
*
|
|
4
|
-
* Lifecycle helpers never write source directly. They emit a structured plan
|
|
5
|
-
* containing:
|
|
6
|
-
* - replaceOps: safe `replace` plan operations against the profile's
|
|
7
|
-
* key-table file, barrels, and other registry files.
|
|
8
|
-
* - manualSteps: things the planned-operation model cannot do today
|
|
9
|
-
* (rename a folder, delete a folder) when folder ops are
|
|
10
|
-
* not requested or not safe.
|
|
11
|
-
* - conflicts: anchors that could not be found — surfaced as advisory
|
|
12
|
-
* hints (no failure).
|
|
13
|
-
* - destructive: `true` for remove; `true` for rename when files need to
|
|
14
|
-
* be renamed on disk.
|
|
15
|
-
*
|
|
16
|
-
* The engine has no project-specific knowledge: every path / key style / barrel
|
|
17
|
-
* comes from the supplied `IPluginLifecycleProfile`. A pack contributes the
|
|
18
|
-
* profile via `pluginLifecycleProfileFiles` on the manifest.
|
|
19
|
-
*/
|
|
20
|
-
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
21
|
-
import { checkFolderOpSafety } from '@shrkcrft/generator';
|
|
22
|
-
import { join } from 'node:path';
|
|
23
|
-
import { CaseStyle, } from '@shrkcrft/plugin-api';
|
|
24
|
-
export var PluginLifecycleAction;
|
|
25
|
-
(function (PluginLifecycleAction) {
|
|
26
|
-
PluginLifecycleAction["Rename"] = "rename";
|
|
27
|
-
PluginLifecycleAction["Remove"] = "remove";
|
|
28
|
-
})(PluginLifecycleAction || (PluginLifecycleAction = {}));
|
|
29
|
-
function readFileSafe(absPath) {
|
|
30
|
-
if (!existsSync(absPath))
|
|
31
|
-
return null;
|
|
32
|
-
try {
|
|
33
|
-
return readFileSync(absPath, 'utf8');
|
|
34
|
-
}
|
|
35
|
-
catch {
|
|
36
|
-
return null;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
function escapeRegex(s) {
|
|
40
|
-
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Build a global regex that matches `${segment}/${name}` only when
|
|
44
|
-
* `name` ends at a true token boundary (the next character is NOT an
|
|
45
|
-
* identifier-continuation: kebab `-`, underscore `_`, digit, letter, or
|
|
46
|
-
* dot). Prevents `data` from matching inside `dataflow`, `data-flow`, or
|
|
47
|
-
* `data.foo`.
|
|
48
|
-
*/
|
|
49
|
-
function segmentBoundaryRegex(segment, name) {
|
|
50
|
-
return new RegExp(`${escapeRegex(segment)}/${escapeRegex(name)}(?![A-Za-z0-9_\\-.])`, 'g');
|
|
51
|
-
}
|
|
52
|
-
function splitWords(input) {
|
|
53
|
-
if (!input)
|
|
54
|
-
return [];
|
|
55
|
-
// kebab / snake split
|
|
56
|
-
if (input.includes('-') || input.includes('_')) {
|
|
57
|
-
return input.split(/[-_]/).filter(Boolean);
|
|
58
|
-
}
|
|
59
|
-
// camel / pascal split on case boundaries
|
|
60
|
-
return input.split(/(?=[A-Z])/).map((w) => w.toLowerCase()).filter(Boolean);
|
|
61
|
-
}
|
|
62
|
-
function toCase(input, style) {
|
|
63
|
-
const words = splitWords(input);
|
|
64
|
-
if (words.length === 0)
|
|
65
|
-
return input;
|
|
66
|
-
switch (style) {
|
|
67
|
-
case CaseStyle.Kebab:
|
|
68
|
-
return words.map((w) => w.toLowerCase()).join('-');
|
|
69
|
-
case CaseStyle.UpperSnake:
|
|
70
|
-
return words.map((w) => w.toUpperCase()).join('_');
|
|
71
|
-
case CaseStyle.Pascal:
|
|
72
|
-
return words.map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join('');
|
|
73
|
-
case CaseStyle.Camel: {
|
|
74
|
-
return words
|
|
75
|
-
.map((w, i) => i === 0
|
|
76
|
-
? w.toLowerCase()
|
|
77
|
-
: w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())
|
|
78
|
-
.join('');
|
|
79
|
-
}
|
|
80
|
-
default:
|
|
81
|
-
return input;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
function pluginKeysEntryRegex(keyTable, name) {
|
|
85
|
-
const key = toCase(name, keyTable.keyCase);
|
|
86
|
-
const value = toCase(name, keyTable.valueCase);
|
|
87
|
-
return new RegExp(`\\s*${escapeRegex(key)}:\\s*['"]${escapeRegex(value)}['"],?\\n`);
|
|
88
|
-
}
|
|
89
|
-
function findFirstMatch(content, re) {
|
|
90
|
-
if (!content)
|
|
91
|
-
return null;
|
|
92
|
-
const m = content.match(re);
|
|
93
|
-
return m ? m[0] : null;
|
|
94
|
-
}
|
|
95
|
-
function loadProfileContext(projectRoot, profile) {
|
|
96
|
-
const keyTablePath = profile.keyTable?.path ? join(projectRoot, profile.keyTable.path) : null;
|
|
97
|
-
const keyTableContent = keyTablePath ? readFileSafe(keyTablePath) : null;
|
|
98
|
-
const barrels = (profile.barrels ?? []).map((b) => {
|
|
99
|
-
const absPath = join(projectRoot, b.path);
|
|
100
|
-
return { barrel: b, absPath, content: readFileSafe(absPath) };
|
|
101
|
-
});
|
|
102
|
-
return { projectRoot, keyTablePath, keyTableContent, barrels };
|
|
103
|
-
}
|
|
104
|
-
export function buildPluginRenamePlan(input) {
|
|
105
|
-
const { projectRoot, profile, oldName, newName } = input;
|
|
106
|
-
const ctx = loadProfileContext(projectRoot, profile);
|
|
107
|
-
const replaceOps = [];
|
|
108
|
-
const manualSteps = [];
|
|
109
|
-
const conflicts = [];
|
|
110
|
-
// Key-table entry rename
|
|
111
|
-
if (profile.keyTable && ctx.keyTableContent) {
|
|
112
|
-
const keyTablePath = profile.keyTable.path;
|
|
113
|
-
const keysFind = findFirstMatch(ctx.keyTableContent, pluginKeysEntryRegex(profile.keyTable, oldName));
|
|
114
|
-
if (keysFind) {
|
|
115
|
-
const oldKey = toCase(oldName, profile.keyTable.keyCase);
|
|
116
|
-
const newKey = toCase(newName, profile.keyTable.keyCase);
|
|
117
|
-
const oldValue = toCase(oldName, profile.keyTable.valueCase);
|
|
118
|
-
const newValue = toCase(newName, profile.keyTable.valueCase);
|
|
119
|
-
replaceOps.push({
|
|
120
|
-
targetPath: keyTablePath,
|
|
121
|
-
operation: {
|
|
122
|
-
kind: 'replace',
|
|
123
|
-
find: keysFind,
|
|
124
|
-
replaceWith: keysFind
|
|
125
|
-
.replace(new RegExp(escapeRegex(oldKey)), newKey)
|
|
126
|
-
.replace(new RegExp(`['"]${escapeRegex(oldValue)}['"]`), `'${newValue}'`),
|
|
127
|
-
description: `Rename key-table entry ${oldKey} → ${newKey}; '${oldValue}' → '${newValue}'.`,
|
|
128
|
-
},
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
else {
|
|
132
|
-
conflicts.push(`Key-table entry for "${oldName}" not found in ${keyTablePath}.`);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
// Barrel exports: word/token-bounded matching prevents substring
|
|
136
|
-
// overlap (e.g. `data` matching inside `dataflow`). A segment match is
|
|
137
|
-
// valid only when the character after `${segment}/${name}` is NOT a
|
|
138
|
-
// kebab/identifier continuation character (so `data` doesn't pre-match
|
|
139
|
-
// `dataflow`, `data-flow`, or `data_flow`).
|
|
140
|
-
for (const { barrel, content } of ctx.barrels) {
|
|
141
|
-
if (!content)
|
|
142
|
-
continue;
|
|
143
|
-
const segment = barrel.exportSegment ?? 'plugins';
|
|
144
|
-
const segmentMatchRe = segmentBoundaryRegex(segment, oldName);
|
|
145
|
-
if (segmentMatchRe.test(content)) {
|
|
146
|
-
const lines = content.split('\n');
|
|
147
|
-
for (const line of lines) {
|
|
148
|
-
// Reset lastIndex so the global regex doesn't skip lines.
|
|
149
|
-
segmentMatchRe.lastIndex = 0;
|
|
150
|
-
if (segmentMatchRe.test(line)) {
|
|
151
|
-
replaceOps.push({
|
|
152
|
-
targetPath: barrel.path,
|
|
153
|
-
operation: {
|
|
154
|
-
kind: 'replace',
|
|
155
|
-
find: line + '\n',
|
|
156
|
-
replaceWith: line.replace(segmentBoundaryRegex(segment, oldName), `${segment}/${newName}`) + '\n',
|
|
157
|
-
description: `Update barrel export from ${segment}/${oldName} to ${segment}/${newName}.`,
|
|
158
|
-
},
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
else {
|
|
164
|
-
conflicts.push(`No barrel export referencing "${segment}/${oldName}" in ${barrel.path}.`);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
// Plugin folder renames — manual unless folder ops requested at the caller
|
|
168
|
-
const folderOps = [];
|
|
169
|
-
for (const root of profile.pluginRoots) {
|
|
170
|
-
const oldDir = join(projectRoot, root.path, oldName);
|
|
171
|
-
if (existsSync(oldDir)) {
|
|
172
|
-
if (input.emitFolderOps) {
|
|
173
|
-
const safety = checkFolderOpSafety(projectRoot, `${root.path}/${oldName}`, 'rename-folder');
|
|
174
|
-
folderOps.push({
|
|
175
|
-
kind: 'rename-folder',
|
|
176
|
-
targetPath: `${root.path}/${oldName}`,
|
|
177
|
-
newPath: `${root.path}/${newName}`,
|
|
178
|
-
safety: safety.safety,
|
|
179
|
-
...(safety.reason ? { safetyReason: safety.reason } : {}),
|
|
180
|
-
reason: 'Structured rename-folder op. Apply rejects unsafe paths automatically; humans still review.',
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
else {
|
|
184
|
-
manualSteps.push({
|
|
185
|
-
kind: 'rename-folder',
|
|
186
|
-
targetPath: `${root.path}/${oldName}`,
|
|
187
|
-
newPath: `${root.path}/${newName}`,
|
|
188
|
-
reason: 'Rename plugin folder. Plan v2 has no rename-folder op by default; pass `--emit-folder-ops` to include them in the plan.',
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
return {
|
|
194
|
-
schema: 'sharkcraft.plugin-lifecycle/v1',
|
|
195
|
-
action: PluginLifecycleAction.Rename,
|
|
196
|
-
profile: profile.id,
|
|
197
|
-
oldName,
|
|
198
|
-
newName,
|
|
199
|
-
destructive: manualSteps.length > 0 || folderOps.length > 0,
|
|
200
|
-
humanApprovalRequired: true,
|
|
201
|
-
replaceOps,
|
|
202
|
-
manualSteps,
|
|
203
|
-
...(folderOps.length > 0 ? { folderOps } : {}),
|
|
204
|
-
conflicts,
|
|
205
|
-
validationCommands: profile.validationCommands ?? [
|
|
206
|
-
'shrk check boundaries --changed-only',
|
|
207
|
-
'shrk doctor',
|
|
208
|
-
],
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
export function buildPluginRemovePlan(input) {
|
|
212
|
-
const { projectRoot, profile, oldName } = input;
|
|
213
|
-
const ctx = loadProfileContext(projectRoot, profile);
|
|
214
|
-
const replaceOps = [];
|
|
215
|
-
const manualSteps = [];
|
|
216
|
-
const folderOps = [];
|
|
217
|
-
const conflicts = [];
|
|
218
|
-
if (profile.keyTable && ctx.keyTableContent) {
|
|
219
|
-
const keyTablePath = profile.keyTable.path;
|
|
220
|
-
const keysFind = findFirstMatch(ctx.keyTableContent, pluginKeysEntryRegex(profile.keyTable, oldName));
|
|
221
|
-
if (keysFind) {
|
|
222
|
-
const oldKey = toCase(oldName, profile.keyTable.keyCase);
|
|
223
|
-
replaceOps.push({
|
|
224
|
-
targetPath: keyTablePath,
|
|
225
|
-
operation: {
|
|
226
|
-
kind: 'replace',
|
|
227
|
-
find: keysFind,
|
|
228
|
-
replaceWith: '',
|
|
229
|
-
description: `Remove key-table entry ${oldKey}.`,
|
|
230
|
-
},
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
else {
|
|
234
|
-
conflicts.push(`Key-table entry for "${oldName}" not found in ${keyTablePath}.`);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
for (const { barrel, content } of ctx.barrels) {
|
|
238
|
-
if (!content)
|
|
239
|
-
continue;
|
|
240
|
-
const segment = barrel.exportSegment ?? 'plugins';
|
|
241
|
-
const segmentMatchRe = segmentBoundaryRegex(segment, oldName);
|
|
242
|
-
const lines = content.split('\n');
|
|
243
|
-
for (const line of lines) {
|
|
244
|
-
segmentMatchRe.lastIndex = 0;
|
|
245
|
-
if (segmentMatchRe.test(line)) {
|
|
246
|
-
replaceOps.push({
|
|
247
|
-
targetPath: barrel.path,
|
|
248
|
-
operation: {
|
|
249
|
-
kind: 'replace',
|
|
250
|
-
find: line + '\n',
|
|
251
|
-
replaceWith: '',
|
|
252
|
-
description: `Remove barrel export referencing ${segment}/${oldName} from ${barrel.path}.`,
|
|
253
|
-
},
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
for (const root of profile.pluginRoots) {
|
|
259
|
-
const oldDir = join(projectRoot, root.path, oldName);
|
|
260
|
-
if (existsSync(oldDir)) {
|
|
261
|
-
if (input.emitFolderOps) {
|
|
262
|
-
const safety = checkFolderOpSafety(projectRoot, `${root.path}/${oldName}`, 'delete-folder');
|
|
263
|
-
folderOps.push({
|
|
264
|
-
kind: 'delete-folder',
|
|
265
|
-
targetPath: `${root.path}/${oldName}`,
|
|
266
|
-
safety: safety.safety,
|
|
267
|
-
...(safety.reason ? { safetyReason: safety.reason } : {}),
|
|
268
|
-
reason: 'Structured delete-folder op. Apply rejects unsafe paths and requires `--allow-delete-folder`.',
|
|
269
|
-
});
|
|
270
|
-
}
|
|
271
|
-
else {
|
|
272
|
-
manualSteps.push({
|
|
273
|
-
kind: 'delete-folder',
|
|
274
|
-
targetPath: `${root.path}/${oldName}`,
|
|
275
|
-
reason: 'Delete plugin folder. Destructive; plan v2 has no delete-folder op by default. Use `git rm -r` after the plan is applied, or pass `--emit-folder-ops`.',
|
|
276
|
-
});
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
return {
|
|
281
|
-
schema: 'sharkcraft.plugin-lifecycle/v1',
|
|
282
|
-
action: PluginLifecycleAction.Remove,
|
|
283
|
-
profile: profile.id,
|
|
284
|
-
oldName,
|
|
285
|
-
destructive: true,
|
|
286
|
-
humanApprovalRequired: true,
|
|
287
|
-
replaceOps,
|
|
288
|
-
manualSteps,
|
|
289
|
-
...(folderOps.length > 0 ? { folderOps } : {}),
|
|
290
|
-
conflicts,
|
|
291
|
-
validationCommands: profile.validationCommands ?? [
|
|
292
|
-
'shrk check boundaries --changed-only',
|
|
293
|
-
'shrk doctor',
|
|
294
|
-
],
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
/**
|
|
298
|
-
* Convert a plugin-lifecycle plan into a saved plan (synthetic
|
|
299
|
-
* templateId) so it can flow through `shrk apply`. ReplaceOps become
|
|
300
|
-
* `expectedChanges` carrying their operation intent; folderOps[] is copied
|
|
301
|
-
* through. The plan is unsigned by this helper; call `signPlan` separately.
|
|
302
|
-
*/
|
|
303
|
-
export const PLUGIN_LIFECYCLE_SYNTHETIC_TEMPLATE = '__plugin-lifecycle__';
|
|
304
|
-
export function pluginLifecyclePlanToSavedPlan(plan, projectRoot) {
|
|
305
|
-
// Drop redundant no-op replace entries (find == replaceWith).
|
|
306
|
-
const seen = new Set();
|
|
307
|
-
const filteredReplaceOps = plan.replaceOps.filter((op) => {
|
|
308
|
-
if (op.operation.find === op.operation.replaceWith)
|
|
309
|
-
return false;
|
|
310
|
-
const key = `${op.targetPath}::${op.operation.find}`;
|
|
311
|
-
if (seen.has(key))
|
|
312
|
-
return false;
|
|
313
|
-
seen.add(key);
|
|
314
|
-
return true;
|
|
315
|
-
});
|
|
316
|
-
// Compute the post-apply file size for each replace op against the
|
|
317
|
-
// current file, so apply-time divergence detection doesn't false-positive
|
|
318
|
-
// size-changes against a sentinel value.
|
|
319
|
-
const expectedChanges = filteredReplaceOps.map((op) => {
|
|
320
|
-
const abs = join(projectRoot, op.targetPath);
|
|
321
|
-
let sizeBytes = Buffer.byteLength(op.operation.replaceWith, 'utf8');
|
|
322
|
-
const existing = readFileSafe(abs);
|
|
323
|
-
if (existing !== null) {
|
|
324
|
-
// Compute the size as the file would look after the replace at save
|
|
325
|
-
// time. Apply will re-evaluate against the (potentially newer) file
|
|
326
|
-
// and `diffPlanChanges` compares this to the size produced by the
|
|
327
|
-
// live evaluator.
|
|
328
|
-
const findCount = (existing.match(new RegExp(escapeRegex(op.operation.find), 'g')) ?? []).length;
|
|
329
|
-
if (findCount === 1) {
|
|
330
|
-
const next = existing.replace(op.operation.find, op.operation.replaceWith);
|
|
331
|
-
sizeBytes = Buffer.byteLength(next, 'utf8');
|
|
332
|
-
}
|
|
333
|
-
else {
|
|
334
|
-
sizeBytes = Buffer.byteLength(existing, 'utf8');
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
return {
|
|
338
|
-
type: 'replace',
|
|
339
|
-
relativePath: op.targetPath,
|
|
340
|
-
sizeBytes,
|
|
341
|
-
operation: op.operation,
|
|
342
|
-
};
|
|
343
|
-
});
|
|
344
|
-
const out = {
|
|
345
|
-
schema: 'sharkcraft.plan/v2',
|
|
346
|
-
templateId: PLUGIN_LIFECYCLE_SYNTHETIC_TEMPLATE,
|
|
347
|
-
variables: {
|
|
348
|
-
profile: plan.profile,
|
|
349
|
-
oldName: plan.oldName,
|
|
350
|
-
...(plan.newName ? { newName: plan.newName } : {}),
|
|
351
|
-
action: plan.action,
|
|
352
|
-
},
|
|
353
|
-
projectRoot,
|
|
354
|
-
createdAt: new Date().toISOString(),
|
|
355
|
-
expectedChanges,
|
|
356
|
-
};
|
|
357
|
-
if (plan.folderOps && plan.folderOps.length > 0) {
|
|
358
|
-
out.folderOps = plan.folderOps.map((fo) => {
|
|
359
|
-
const entry = {
|
|
360
|
-
kind: fo.kind,
|
|
361
|
-
targetPath: fo.targetPath,
|
|
362
|
-
reason: fo.reason,
|
|
363
|
-
};
|
|
364
|
-
if (fo.newPath !== undefined)
|
|
365
|
-
entry.newPath = fo.newPath;
|
|
366
|
-
return entry;
|
|
367
|
-
});
|
|
368
|
-
}
|
|
369
|
-
return out;
|
|
370
|
-
}
|
|
371
|
-
export function renderPluginLifecyclePlanText(plan) {
|
|
372
|
-
const lines = [];
|
|
373
|
-
lines.push(`=== Plugin ${plan.action} (${plan.profile} profile) ===`);
|
|
374
|
-
lines.push(` oldName ${plan.oldName}`);
|
|
375
|
-
if (plan.newName)
|
|
376
|
-
lines.push(` newName ${plan.newName}`);
|
|
377
|
-
lines.push(` destructive ${plan.destructive ? 'YES' : 'no'}`);
|
|
378
|
-
lines.push(` approval ${plan.humanApprovalRequired ? 'human review required' : 'auto'}`);
|
|
379
|
-
lines.push('');
|
|
380
|
-
lines.push(`Planned replace ops (${plan.replaceOps.length}):`);
|
|
381
|
-
for (const op of plan.replaceOps) {
|
|
382
|
-
lines.push(` • ${op.targetPath}`);
|
|
383
|
-
lines.push(` ${op.operation.description}`);
|
|
384
|
-
}
|
|
385
|
-
if (plan.manualSteps.length > 0) {
|
|
386
|
-
lines.push('');
|
|
387
|
-
lines.push(`Manual steps required (${plan.manualSteps.length}):`);
|
|
388
|
-
for (const step of plan.manualSteps) {
|
|
389
|
-
lines.push(` • [${step.kind}] ${step.targetPath}${step.newPath ? ` → ${step.newPath}` : ''}`);
|
|
390
|
-
lines.push(` ${step.reason}`);
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
if (plan.conflicts.length > 0) {
|
|
394
|
-
lines.push('');
|
|
395
|
-
lines.push(`Conflicts (${plan.conflicts.length}):`);
|
|
396
|
-
for (const c of plan.conflicts)
|
|
397
|
-
lines.push(` • ${c}`);
|
|
398
|
-
}
|
|
399
|
-
lines.push('');
|
|
400
|
-
lines.push(`Validation commands:`);
|
|
401
|
-
for (const c of plan.validationCommands)
|
|
402
|
-
lines.push(` $ ${c}`);
|
|
403
|
-
return lines.join('\n') + '\n';
|
|
404
|
-
}
|
|
405
|
-
export function buildPluginLifecycleListing(input) {
|
|
406
|
-
const { projectRoot, profile } = input;
|
|
407
|
-
const layers = {};
|
|
408
|
-
for (const root of profile.pluginRoots) {
|
|
409
|
-
const dir = join(projectRoot, root.path);
|
|
410
|
-
if (!existsSync(dir))
|
|
411
|
-
continue;
|
|
412
|
-
let entries;
|
|
413
|
-
try {
|
|
414
|
-
entries = readdirSync(dir, { withFileTypes: true });
|
|
415
|
-
}
|
|
416
|
-
catch {
|
|
417
|
-
continue;
|
|
418
|
-
}
|
|
419
|
-
layers[root.path] = entries
|
|
420
|
-
.filter((e) => e.isDirectory())
|
|
421
|
-
.map((e) => String(e.name));
|
|
422
|
-
}
|
|
423
|
-
const pluginKeys = [];
|
|
424
|
-
if (profile.keyTable) {
|
|
425
|
-
const keyTableContent = readFileSafe(join(projectRoot, profile.keyTable.path));
|
|
426
|
-
if (keyTableContent) {
|
|
427
|
-
const re = /(^|\n)\s*([A-Za-z_][A-Za-z0-9_]*):\s*'([A-Za-z_][A-Za-z0-9_-]*)'/g;
|
|
428
|
-
let match;
|
|
429
|
-
while ((match = re.exec(keyTableContent)) !== null) {
|
|
430
|
-
pluginKeys.push({ key: match[2], value: match[3] });
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
return { pluginsByLayer: layers, pluginKeys };
|
|
435
|
-
}
|
|
436
|
-
export function checkPluginLifecycleProfileHealth(projectRoot, profile) {
|
|
437
|
-
const out = [];
|
|
438
|
-
if (profile.keyTable) {
|
|
439
|
-
const abs = join(projectRoot, profile.keyTable.path);
|
|
440
|
-
if (!existsSync(abs)) {
|
|
441
|
-
out.push({
|
|
442
|
-
id: 'missing-key-table',
|
|
443
|
-
severity: 'warning',
|
|
444
|
-
path: profile.keyTable.path,
|
|
445
|
-
message: `keyTable.path "${profile.keyTable.path}" not found in this workspace.`,
|
|
446
|
-
});
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
for (const b of profile.barrels ?? []) {
|
|
450
|
-
if (!existsSync(join(projectRoot, b.path))) {
|
|
451
|
-
out.push({
|
|
452
|
-
id: 'missing-barrel',
|
|
453
|
-
severity: 'warning',
|
|
454
|
-
path: b.path,
|
|
455
|
-
message: `Barrel "${b.id}" path "${b.path}" not found.`,
|
|
456
|
-
});
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
for (const r of profile.pluginRoots) {
|
|
460
|
-
if (!existsSync(join(projectRoot, r.path))) {
|
|
461
|
-
out.push({
|
|
462
|
-
id: 'missing-plugin-root',
|
|
463
|
-
severity: 'warning',
|
|
464
|
-
path: r.path,
|
|
465
|
-
message: `Plugin root "${r.id}" path "${r.path}" not found.`,
|
|
466
|
-
});
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
if (out.length === 0) {
|
|
470
|
-
out.push({
|
|
471
|
-
id: 'ok',
|
|
472
|
-
severity: 'info',
|
|
473
|
-
message: `Profile "${profile.id}" looks healthy in this workspace.`,
|
|
474
|
-
});
|
|
475
|
-
}
|
|
476
|
-
return out;
|
|
477
|
-
}
|