renma 0.18.2 → 0.18.3
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 +55 -1
- package/architecture.md +4 -0
- package/dist/catalog.d.ts +1 -1
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +5 -2
- package/dist/catalog.js.map +1 -1
- package/dist/commands/inspect.d.ts +2 -66
- package/dist/commands/inspect.d.ts.map +1 -1
- package/dist/commands/inspect.js +45 -295
- package/dist/commands/inspect.js.map +1 -1
- package/dist/commands/readiness.d.ts +3 -0
- package/dist/commands/readiness.d.ts.map +1 -1
- package/dist/commands/readiness.js +9 -6
- package/dist/commands/readiness.js.map +1 -1
- package/dist/commands/suggest-metadata.d.ts +4 -21
- package/dist/commands/suggest-metadata.d.ts.map +1 -1
- package/dist/commands/suggest-metadata.js +65 -514
- package/dist/commands/suggest-metadata.js.map +1 -1
- package/dist/decisions/metadata-suggestion.d.ts +60 -0
- package/dist/decisions/metadata-suggestion.d.ts.map +1 -0
- package/dist/decisions/metadata-suggestion.js +176 -0
- package/dist/decisions/metadata-suggestion.js.map +1 -0
- package/dist/discovery.d.ts.map +1 -1
- package/dist/discovery.js +4 -0
- package/dist/discovery.js.map +1 -1
- package/dist/evidence/classification.d.ts +4 -0
- package/dist/evidence/classification.d.ts.map +1 -0
- package/dist/evidence/classification.js +15 -0
- package/dist/evidence/classification.js.map +1 -0
- package/dist/evidence/inspect.d.ts +68 -0
- package/dist/evidence/inspect.d.ts.map +1 -0
- package/dist/evidence/inspect.js +2 -0
- package/dist/evidence/inspect.js.map +1 -0
- package/dist/evidence/target.d.ts +51 -0
- package/dist/evidence/target.d.ts.map +1 -0
- package/dist/evidence/target.js +161 -0
- package/dist/evidence/target.js.map +1 -0
- package/dist/renderers/inspect.d.ts +3 -0
- package/dist/renderers/inspect.d.ts.map +1 -0
- package/dist/renderers/inspect.js +159 -0
- package/dist/renderers/inspect.js.map +1 -0
- package/dist/renderers/metadata-suggestion.d.ts +3 -0
- package/dist/renderers/metadata-suggestion.d.ts.map +1 -0
- package/dist/renderers/metadata-suggestion.js +320 -0
- package/dist/renderers/metadata-suggestion.js.map +1 -0
- package/dist/repository-evidence.d.ts +7 -1
- package/dist/repository-evidence.d.ts.map +1 -1
- package/dist/repository-evidence.js +10 -2
- package/dist/repository-evidence.js.map +1 -1
- package/dist/scanner.d.ts.map +1 -1
- package/dist/scanner.js +3 -13
- package/dist/scanner.js.map +1 -1
- package/docs/README.md +2 -0
- package/docs/internal-architecture.md +296 -0
- package/package.json +1 -1
- package/scripts/verify-package.mjs +154 -9
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
import { lstat,
|
|
1
|
+
import { lstat, realpath, stat } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { buildSkillParentIndex, resolveSkillSupportParent, withResolvedSkillParent, } from "../catalog.js";
|
|
4
3
|
import { renmaCommand } from "../command-invocation.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
import { buildAgentSkillMigrationSuggestion, } from "../skill-migration.js";
|
|
4
|
+
import { buildMetadataCandidateSuggestionDecision, buildSkillLocalParentUnresolvedDecision, buildSkillLocalSuggestionDecision, buildSkillSuggestionDecision, buildUnsupportedTargetSuggestionDecision, evaluateOwnerConflict, } from "../decisions/metadata-suggestion.js";
|
|
5
|
+
import { collectTargetDocumentEvidence, collectTargetRepositoryEvidence, } from "../evidence/target.js";
|
|
6
|
+
import { buildAgentSkillMigrationSuggestion } from "../skill-migration.js";
|
|
7
|
+
import { renderMetadataPrompt } from "../renderers/metadata-suggestion.js";
|
|
8
|
+
export { renderMetadataPrompt };
|
|
11
9
|
export class SuggestMetadataTargetError extends Error {
|
|
12
10
|
constructor(target, cause) {
|
|
13
11
|
super(`Could not read metadata target ${target}: ${readErrorReason(cause)}`);
|
|
@@ -23,40 +21,17 @@ export async function runSuggestMetadataCommand(target, options = {}) {
|
|
|
23
21
|
return 0;
|
|
24
22
|
}
|
|
25
23
|
export async function buildMetadataSuggestion(target, options = {}) {
|
|
26
|
-
|
|
27
|
-
let content;
|
|
24
|
+
let targetEvidence;
|
|
28
25
|
try {
|
|
29
|
-
|
|
26
|
+
targetEvidence = await collectTargetDocumentEvidence(target, {
|
|
27
|
+
unresolvedArtifactPath: "input",
|
|
28
|
+
});
|
|
30
29
|
}
|
|
31
30
|
catch (error) {
|
|
32
31
|
throw new SuggestMetadataTargetError(target, error);
|
|
33
32
|
}
|
|
34
|
-
const outputPath =
|
|
35
|
-
|
|
36
|
-
const classificationPath = classificationContext.state === "resolved"
|
|
37
|
-
? classificationContext.relativePath
|
|
38
|
-
: "";
|
|
39
|
-
const repositoryRoot = classificationContext.state === "resolved"
|
|
40
|
-
? classificationContext.root
|
|
41
|
-
: undefined;
|
|
42
|
-
const entrypoint = classificationPath
|
|
43
|
-
? classifyRepositorySkillEntrypointPath(classificationPath)
|
|
44
|
-
: undefined;
|
|
45
|
-
const initialClassification = classifyAssetPath(classificationPath);
|
|
46
|
-
const initialKind = initialClassification.kind;
|
|
47
|
-
const document = parseDocument({
|
|
48
|
-
path: classificationPath || outputPath,
|
|
49
|
-
absolutePath,
|
|
50
|
-
kind: initialKind,
|
|
51
|
-
sizeBytes: Buffer.byteLength(content),
|
|
52
|
-
contentClassification: "text",
|
|
53
|
-
markdownParserEligible: /\.mdx?$/i.test(outputPath),
|
|
54
|
-
content,
|
|
55
|
-
});
|
|
56
|
-
const { metadata } = parseAssetMetadata(document);
|
|
57
|
-
let classification = classifyAssetPath(classificationPath, {
|
|
58
|
-
...(metadata.type ? { metadataType: metadata.type } : {}),
|
|
59
|
-
});
|
|
33
|
+
const { absolutePath, outputPath, repositoryBoundary: classificationContext, repositoryRelativePath: classificationPath, repositoryRoot, entrypoint, document, metadata, } = targetEvidence;
|
|
34
|
+
let { classification } = targetEvidence;
|
|
60
35
|
const kind = classification.kind;
|
|
61
36
|
const explicitOwner = optionalText(options.owner);
|
|
62
37
|
if (kind === "skill") {
|
|
@@ -71,7 +46,7 @@ export async function buildMetadataSuggestion(target, options = {}) {
|
|
|
71
46
|
const noMigrationProposed = agentSkills.proposalKind === "none" &&
|
|
72
47
|
agentSkills.sourceFormat === "agent-skills";
|
|
73
48
|
const metadataRetrofit = agentSkills.proposalKind === "canonical-metadata-retrofit";
|
|
74
|
-
const decision =
|
|
49
|
+
const decision = buildSkillSuggestionDecision(agentSkills);
|
|
75
50
|
return {
|
|
76
51
|
path: outputPath,
|
|
77
52
|
kind,
|
|
@@ -125,9 +100,7 @@ export async function buildMetadataSuggestion(target, options = {}) {
|
|
|
125
100
|
const candidateId = inferCandidateId(kind, classificationPath);
|
|
126
101
|
const candidateTitle = mainHeadingTitle(document.headings);
|
|
127
102
|
if (classification.scope === "skill-local") {
|
|
128
|
-
const localContext = await
|
|
129
|
-
? classificationContext.root
|
|
130
|
-
: undefined);
|
|
103
|
+
const localContext = resolveSkillLocalContext(await collectTargetRepositoryEvidence(targetEvidence));
|
|
131
104
|
classification = localContext.classification;
|
|
132
105
|
if (localContext.parent.state !== "resolved") {
|
|
133
106
|
const parentState = localContext.parent.state === "not-applicable"
|
|
@@ -139,16 +112,13 @@ export async function buildMetadataSuggestion(target, options = {}) {
|
|
|
139
112
|
reason: `The structural parent Skill is ${parentState}; inheritance is not established. Review the repository layout before adding local metadata.`,
|
|
140
113
|
},
|
|
141
114
|
];
|
|
115
|
+
const decision = buildSkillLocalParentUnresolvedDecision();
|
|
142
116
|
return {
|
|
143
117
|
path: outputPath,
|
|
144
118
|
kind,
|
|
145
119
|
suggestedMode: "no-proposal",
|
|
146
|
-
decisionStatus:
|
|
147
|
-
decision:
|
|
148
|
-
reasonCode: "skill-local-parent-unresolved",
|
|
149
|
-
summary: "Renma cannot confirm one parent Skill, so it cannot claim inherited governance or safely propose an independent local override.",
|
|
150
|
-
question: "Resolve the missing or ambiguous parent Skill layout, then rerun this command.",
|
|
151
|
-
},
|
|
120
|
+
decisionStatus: decision.status,
|
|
121
|
+
decision: decision.decision,
|
|
152
122
|
classification,
|
|
153
123
|
ownerProvided: Boolean(explicitOwner),
|
|
154
124
|
instructions: skillLocalInstructions(classification, false, true, false),
|
|
@@ -157,45 +127,25 @@ export async function buildMetadataSuggestion(target, options = {}) {
|
|
|
157
127
|
nextActions: suggestionNextActions(outputPath, classification, repositoryRoot),
|
|
158
128
|
};
|
|
159
129
|
}
|
|
160
|
-
const
|
|
130
|
+
const ownerConflict = evaluateOwnerConflict(existingOwner, explicitOwner);
|
|
131
|
+
const blockedMetadata = ownerConflict.blockedMetadata;
|
|
161
132
|
if (!existingOwner && explicitOwner)
|
|
162
133
|
candidateMetadata.owner = explicitOwner;
|
|
163
134
|
const hasOverride = Object.keys(candidateMetadata).length > 0;
|
|
164
135
|
const hasLocalGovernance = Boolean(existingOwner) || localContext.hasLocalPolicyMetadata;
|
|
165
136
|
const inherited = localContext.ownershipSource === "inherited";
|
|
137
|
+
const decision = buildSkillLocalSuggestionDecision({
|
|
138
|
+
hasOwnerConflict: ownerConflict.hasConflict,
|
|
139
|
+
hasOverride,
|
|
140
|
+
hasLocalGovernance,
|
|
141
|
+
inheritsGovernance: inherited,
|
|
142
|
+
});
|
|
166
143
|
return {
|
|
167
144
|
path: outputPath,
|
|
168
145
|
kind,
|
|
169
146
|
suggestedMode: hasOverride ? "metadata-retrofit" : "no-proposal",
|
|
170
|
-
decisionStatus:
|
|
171
|
-
|
|
172
|
-
: hasOverride
|
|
173
|
-
? "deterministic"
|
|
174
|
-
: "no-change-recommended",
|
|
175
|
-
decision: blockedMetadata.length > 0
|
|
176
|
-
? {
|
|
177
|
-
reasonCode: "conflicting-ownership-evidence",
|
|
178
|
-
summary: "Renma cannot safely construct a local metadata override while declared and provided ownership evidence conflict.",
|
|
179
|
-
}
|
|
180
|
-
: hasOverride
|
|
181
|
-
? {
|
|
182
|
-
reasonCode: "explicit-human-provided-override",
|
|
183
|
-
summary: "The candidate is an explicit human-provided Skill-local metadata override; it is not required for ordinary local support.",
|
|
184
|
-
}
|
|
185
|
-
: hasLocalGovernance
|
|
186
|
-
? {
|
|
187
|
-
reasonCode: "skill-local-existing-metadata-preserved",
|
|
188
|
-
summary: "Existing explicit local governance metadata is preserved; no inherited-governance claim or retrofit is needed.",
|
|
189
|
-
}
|
|
190
|
-
: inherited
|
|
191
|
-
? {
|
|
192
|
-
reasonCode: "skill-local-governance-inherited",
|
|
193
|
-
summary: "One unambiguous parent Skill supplies effective governance, so no independent metadata retrofit is required.",
|
|
194
|
-
}
|
|
195
|
-
: {
|
|
196
|
-
reasonCode: "skill-local-unowned",
|
|
197
|
-
summary: "The parent Skill is resolved, but neither the local file nor its parent declares an owner; missing ownership remains allowed.",
|
|
198
|
-
},
|
|
147
|
+
decisionStatus: decision.status,
|
|
148
|
+
decision: decision.decision,
|
|
199
149
|
classification,
|
|
200
150
|
ownerProvided: Boolean(explicitOwner),
|
|
201
151
|
instructions: skillLocalInstructions(classification, hasOverride, false, hasLocalGovernance),
|
|
@@ -206,24 +156,18 @@ export async function buildMetadataSuggestion(target, options = {}) {
|
|
|
206
156
|
}
|
|
207
157
|
if (classification.matchedRule === "repository-tool" ||
|
|
208
158
|
classification.matchedRule === "unknown") {
|
|
209
|
-
const
|
|
159
|
+
const decision = buildUnsupportedTargetSuggestionDecision({
|
|
160
|
+
matchedRule: classification.matchedRule,
|
|
161
|
+
...(classificationContext.state === "unresolved"
|
|
162
|
+
? { boundaryReasonCode: classificationContext.reasonCode }
|
|
163
|
+
: {}),
|
|
164
|
+
});
|
|
210
165
|
return {
|
|
211
166
|
path: outputPath,
|
|
212
167
|
kind,
|
|
213
168
|
suggestedMode: "no-proposal",
|
|
214
|
-
decisionStatus:
|
|
215
|
-
decision:
|
|
216
|
-
? {
|
|
217
|
-
reasonCode: classificationContext.reasonCode,
|
|
218
|
-
summary: "Renma could not infer one safe repository-relative boundary for this target path.",
|
|
219
|
-
}
|
|
220
|
-
: {
|
|
221
|
-
reasonCode: classification.matchedRule === "repository-tool"
|
|
222
|
-
? "repository-tool-not-context"
|
|
223
|
-
: "outside-recognized-asset-boundary",
|
|
224
|
-
summary: "No metadata proposal is generated because the target is not an independently governed Renma asset.",
|
|
225
|
-
question: "Is this file intended to have independent ownership and lifecycle under a recognized asset root?",
|
|
226
|
-
},
|
|
169
|
+
decisionStatus: decision.status,
|
|
170
|
+
decision: decision.decision,
|
|
227
171
|
classification,
|
|
228
172
|
ownerProvided: Boolean(explicitOwner),
|
|
229
173
|
instructions: [
|
|
@@ -245,7 +189,8 @@ export async function buildMetadataSuggestion(target, options = {}) {
|
|
|
245
189
|
if (!existingOwner && explicitOwner) {
|
|
246
190
|
candidateMetadata.owner = explicitOwner;
|
|
247
191
|
}
|
|
248
|
-
const
|
|
192
|
+
const ownerConflict = evaluateOwnerConflict(existingOwner, explicitOwner);
|
|
193
|
+
const blockedMetadata = ownerConflict.blockedMetadata;
|
|
249
194
|
if (!existingOwner && !explicitOwner) {
|
|
250
195
|
blockedMetadata.push({
|
|
251
196
|
field: "owner",
|
|
@@ -253,38 +198,18 @@ export async function buildMetadataSuggestion(target, options = {}) {
|
|
|
253
198
|
});
|
|
254
199
|
}
|
|
255
200
|
const hasCandidate = Object.keys(candidateMetadata).length > 0;
|
|
256
|
-
const hasConflict =
|
|
201
|
+
const hasConflict = ownerConflict.hasConflict;
|
|
202
|
+
const decision = buildMetadataCandidateSuggestionDecision({
|
|
203
|
+
hasOwnerConflict: hasConflict,
|
|
204
|
+
hasCandidate,
|
|
205
|
+
scope: classification.scope,
|
|
206
|
+
});
|
|
257
207
|
return {
|
|
258
208
|
path: outputPath,
|
|
259
209
|
kind,
|
|
260
210
|
suggestedMode: hasConflict || !hasCandidate ? "no-proposal" : "metadata-retrofit",
|
|
261
|
-
decisionStatus:
|
|
262
|
-
|
|
263
|
-
: hasCandidate
|
|
264
|
-
? classification.scope === "independent"
|
|
265
|
-
? "human-confirmation-required"
|
|
266
|
-
: "deterministic"
|
|
267
|
-
: "no-change-recommended",
|
|
268
|
-
decision: hasConflict
|
|
269
|
-
? {
|
|
270
|
-
reasonCode: "conflicting-ownership-evidence",
|
|
271
|
-
summary: "Renma cannot safely construct a metadata proposal.",
|
|
272
|
-
}
|
|
273
|
-
: hasCandidate && classification.scope === "independent"
|
|
274
|
-
? {
|
|
275
|
-
reasonCode: "independent-governance-intent-unconfirmed",
|
|
276
|
-
summary: "Renma constructed only deterministic candidates; the intended owner, lifecycle, and source-of-truth evidence still require human confirmation.",
|
|
277
|
-
question: "Confirm the intended owner, lifecycle, and source-of-truth evidence for this independent asset.",
|
|
278
|
-
}
|
|
279
|
-
: hasCandidate
|
|
280
|
-
? {
|
|
281
|
-
reasonCode: "deterministic-metadata-candidate",
|
|
282
|
-
summary: "The metadata candidate follows the classified repository boundary and explicit user evidence.",
|
|
283
|
-
}
|
|
284
|
-
: {
|
|
285
|
-
reasonCode: "metadata-already-sufficient",
|
|
286
|
-
summary: "Renma found no supported metadata change to propose.",
|
|
287
|
-
},
|
|
211
|
+
decisionStatus: decision.status,
|
|
212
|
+
decision: decision.decision,
|
|
288
213
|
classification,
|
|
289
214
|
ownerProvided: Boolean(explicitOwner),
|
|
290
215
|
instructions: buildInstructions({ existingOwner, explicitOwner }),
|
|
@@ -293,227 +218,6 @@ export async function buildMetadataSuggestion(target, options = {}) {
|
|
|
293
218
|
nextActions: suggestionNextActions(outputPath, classification, repositoryRoot),
|
|
294
219
|
};
|
|
295
220
|
}
|
|
296
|
-
export function renderMetadataPrompt(suggestion) {
|
|
297
|
-
if (suggestion.agentSkills)
|
|
298
|
-
return renderAgentSkillMigrationPrompt(suggestion);
|
|
299
|
-
const blocked = suggestion.decisionStatus === "blocked";
|
|
300
|
-
const noChange = suggestion.decisionStatus === "no-change-recommended";
|
|
301
|
-
const noProposal = blocked || noChange;
|
|
302
|
-
const skillLocal = suggestion.classification.scope === "skill-local";
|
|
303
|
-
return `${[
|
|
304
|
-
blocked
|
|
305
|
-
? "# Renma Result: Metadata Proposal Blocked"
|
|
306
|
-
: noProposal
|
|
307
|
-
? "# Renma Result: No Metadata Proposal"
|
|
308
|
-
: "# Renma Task: Safely Retrofit Renma Metadata",
|
|
309
|
-
"",
|
|
310
|
-
blocked
|
|
311
|
-
? "Renma cannot safely produce an applicable metadata patch."
|
|
312
|
-
: noProposal
|
|
313
|
-
? "No independent metadata retrofit is required."
|
|
314
|
-
: "Review this existing Renma asset metadata candidate safely.",
|
|
315
|
-
"",
|
|
316
|
-
"Asset:",
|
|
317
|
-
`- Path: \`${suggestion.path}\``,
|
|
318
|
-
`- Kind: \`${suggestion.kind}\``,
|
|
319
|
-
`- Mode: \`${suggestion.suggestedMode}\``,
|
|
320
|
-
`- Decision status: \`${suggestion.decisionStatus}\``,
|
|
321
|
-
"",
|
|
322
|
-
"Classification:",
|
|
323
|
-
...classificationPromptLines(suggestion.classification),
|
|
324
|
-
"",
|
|
325
|
-
"Observed repository fact:",
|
|
326
|
-
observedRepositoryFact(suggestion.classification),
|
|
327
|
-
"",
|
|
328
|
-
"Deterministic Renma interpretation:",
|
|
329
|
-
deterministicInterpretation(suggestion.classification),
|
|
330
|
-
"",
|
|
331
|
-
noProposal ? "Recommendation:" : "Decision:",
|
|
332
|
-
suggestion.decision.summary,
|
|
333
|
-
...(suggestion.decision.question
|
|
334
|
-
? ["", "Remaining human decision:", suggestion.decision.question]
|
|
335
|
-
: []),
|
|
336
|
-
...(skillLocal
|
|
337
|
-
? suggestion.classification.parentResolution === "resolved"
|
|
338
|
-
? [
|
|
339
|
-
"",
|
|
340
|
-
"This file is structurally Skill-local support with one resolved parent:",
|
|
341
|
-
suggestion.classification.parentAssetPath ??
|
|
342
|
-
"(resolved parent path unavailable)",
|
|
343
|
-
"",
|
|
344
|
-
...skillLocalGovernancePromptLines(suggestion),
|
|
345
|
-
"Preserve existing metadata if present.",
|
|
346
|
-
"Do not add an owner, move the file, or create a patch solely to manufacture work.",
|
|
347
|
-
]
|
|
348
|
-
: [
|
|
349
|
-
"",
|
|
350
|
-
"This file is structurally Skill-local support, but its parent governance is unresolved.",
|
|
351
|
-
`Parent resolution: ${suggestion.classification.parentResolution ?? "structural-candidate"}.`,
|
|
352
|
-
"Do not claim inheritance or add an independent local owner until the repository layout resolves to one parent Skill.",
|
|
353
|
-
]
|
|
354
|
-
: []),
|
|
355
|
-
"",
|
|
356
|
-
"Rules:",
|
|
357
|
-
...suggestion.instructions.map((instruction) => `- ${instruction}`),
|
|
358
|
-
"",
|
|
359
|
-
blocked
|
|
360
|
-
? "Candidate Metadata (not applicable while blocked):"
|
|
361
|
-
: "Candidate Metadata:",
|
|
362
|
-
...(blocked
|
|
363
|
-
? ["- (suppressed because the decision is blocked)"]
|
|
364
|
-
: metadataLines(suggestion.candidateMetadata)),
|
|
365
|
-
"",
|
|
366
|
-
"Blocked Metadata:",
|
|
367
|
-
...blockedMetadataLines(suggestion.blockedMetadata),
|
|
368
|
-
"",
|
|
369
|
-
"Verification:",
|
|
370
|
-
...(blocked
|
|
371
|
-
? suggestion.nextActions.length > 0
|
|
372
|
-
? [
|
|
373
|
-
"- Resolve the blocked evidence and rerun `renma suggest-metadata`.",
|
|
374
|
-
"- Run only the structured safe action; do not apply a patch from this result.",
|
|
375
|
-
]
|
|
376
|
-
: [
|
|
377
|
-
"- Establish the repository root with an explicit root or repository marker, then rerun `renma suggest-metadata`.",
|
|
378
|
-
"- No verification command is safe while the repository boundary is unresolved.",
|
|
379
|
-
]
|
|
380
|
-
: noChange
|
|
381
|
-
? ["- No verification change is required when no file change is made."]
|
|
382
|
-
: [
|
|
383
|
-
"- Run `renma scan . --fail-on high --format json`.",
|
|
384
|
-
"- Run `renma ownership .`.",
|
|
385
|
-
]),
|
|
386
|
-
"",
|
|
387
|
-
blocked
|
|
388
|
-
? "Do not return or apply a patch while the decision is blocked. Preserve the existing source."
|
|
389
|
-
: noChange
|
|
390
|
-
? "Stop without manufacturing work. Preserve the existing source."
|
|
391
|
-
: suggestion.decisionStatus === "human-confirmation-required"
|
|
392
|
-
? "After confirming the stated human decision, return only the reviewed candidate fields. Do not invent unresolved owner, lifecycle, or source-of-truth metadata, and do not rewrite the asset body."
|
|
393
|
-
: "Return a small reviewed patch containing only the deterministic candidate. Do not rewrite the asset body.",
|
|
394
|
-
].join("\n")}\n`;
|
|
395
|
-
}
|
|
396
|
-
function renderAgentSkillMigrationPrompt(suggestion) {
|
|
397
|
-
const migration = suggestion.agentSkills;
|
|
398
|
-
if (!migration)
|
|
399
|
-
return "";
|
|
400
|
-
const metadataRetrofit = migration.proposalKind === "canonical-metadata-retrofit";
|
|
401
|
-
const noMigrationProposed = migration.proposalKind === "none" &&
|
|
402
|
-
migration.sourceFormat === "agent-skills";
|
|
403
|
-
const candidate = suggestion.decisionStatus !== "blocked" && migration.canonicalFrontmatter
|
|
404
|
-
? [
|
|
405
|
-
"Canonical Frontmatter Candidate:",
|
|
406
|
-
"",
|
|
407
|
-
"```yaml",
|
|
408
|
-
migration.canonicalFrontmatter,
|
|
409
|
-
"```",
|
|
410
|
-
]
|
|
411
|
-
: [
|
|
412
|
-
"Canonical Frontmatter Candidate:",
|
|
413
|
-
"",
|
|
414
|
-
"(not generated while migration is blocked or unnecessary)",
|
|
415
|
-
];
|
|
416
|
-
const promptState = suggestion.decisionStatus === "blocked"
|
|
417
|
-
? "blocked"
|
|
418
|
-
: suggestion.decisionStatus === "no-change-recommended"
|
|
419
|
-
? "no-proposal"
|
|
420
|
-
: "candidate";
|
|
421
|
-
const verification = renderSkillSuggestionVerification(promptState);
|
|
422
|
-
const nextSteps = renderSkillSuggestionNextSteps(promptState);
|
|
423
|
-
return `${[
|
|
424
|
-
noMigrationProposed
|
|
425
|
-
? "# Renma Task: Inspect Canonical Agent Skill (No Migration Proposed)"
|
|
426
|
-
: metadataRetrofit
|
|
427
|
-
? "# Renma Task: Review Canonical Agent Skills Metadata Retrofit"
|
|
428
|
-
: "# Renma Task: Review One-Way Agent Skills Migration",
|
|
429
|
-
"",
|
|
430
|
-
`Asset: \`${suggestion.path}\``,
|
|
431
|
-
`Decision status: \`${suggestion.decisionStatus}\``,
|
|
432
|
-
`Decision reason: \`${suggestion.decision.reasonCode}\``,
|
|
433
|
-
"Classification:",
|
|
434
|
-
...classificationPromptLines(suggestion.classification),
|
|
435
|
-
`Source format: \`${migration.sourceFormat}\``,
|
|
436
|
-
`Direction: \`${migration.direction}\``,
|
|
437
|
-
`Proposal: \`${migration.proposalKind}\``,
|
|
438
|
-
`Source path: \`${migration.sourcePath}\``,
|
|
439
|
-
`Target path: \`${migration.targetPath}\``,
|
|
440
|
-
`Entrypoint migration: \`${migration.entrypointMigration}\``,
|
|
441
|
-
"",
|
|
442
|
-
"Rules:",
|
|
443
|
-
...suggestion.instructions.map((instruction) => `- ${instruction}`),
|
|
444
|
-
"",
|
|
445
|
-
"Candidate Agent Skills Fields:",
|
|
446
|
-
...metadataLines(migration.candidateAgentSkillsFields),
|
|
447
|
-
"",
|
|
448
|
-
"Candidate Renma Metadata:",
|
|
449
|
-
...metadataLines(migration.candidateRenmaMetadata),
|
|
450
|
-
"",
|
|
451
|
-
...candidate,
|
|
452
|
-
"",
|
|
453
|
-
"Blocked Migration Evidence:",
|
|
454
|
-
...blockedMetadataLines(suggestion.blockedMetadata),
|
|
455
|
-
"",
|
|
456
|
-
"Human Review:",
|
|
457
|
-
migration.reviewPrompt,
|
|
458
|
-
"",
|
|
459
|
-
...verification,
|
|
460
|
-
"",
|
|
461
|
-
...nextSteps,
|
|
462
|
-
"",
|
|
463
|
-
promptState === "blocked"
|
|
464
|
-
? "Do not return or apply a frontmatter patch while the decision is blocked. Preserve the existing source until every blocked item is resolved with human review."
|
|
465
|
-
: promptState === "no-proposal"
|
|
466
|
-
? "No frontmatter patch is proposed. Do not return or apply a frontmatter patch; preserve the existing source."
|
|
467
|
-
: suggestion.decisionStatus === "human-confirmation-required" &&
|
|
468
|
-
migration.canonicalFrontmatter
|
|
469
|
-
? migration.entrypointMigration === "none"
|
|
470
|
-
? "After a human confirms the intended Skill semantics, return only the reviewed canonical frontmatter candidate. Do not invent unresolved fields or rewrite the Skill body."
|
|
471
|
-
: "After a human confirms the intended Skill semantics, return one reviewed patch containing only the entrypoint path migration and canonical frontmatter candidate. Do not invent unresolved fields or rewrite the Skill body."
|
|
472
|
-
: migration.canonicalFrontmatter
|
|
473
|
-
? migration.entrypointMigration === "none"
|
|
474
|
-
? "Return a small reviewed frontmatter patch. Do not rewrite the Skill body."
|
|
475
|
-
: "Return one small reviewed patch containing both the entrypoint path migration and frontmatter migration. Do not rewrite the Skill body."
|
|
476
|
-
: "Do not return a patch because no canonical candidate was generated.",
|
|
477
|
-
].join("\n")}\n`;
|
|
478
|
-
}
|
|
479
|
-
function renderSkillSuggestionVerification(state) {
|
|
480
|
-
if (state === "no-proposal") {
|
|
481
|
-
return [
|
|
482
|
-
"Verification:",
|
|
483
|
-
"- No verification change is required when no separate authoring change is made.",
|
|
484
|
-
"- Only if a separate, intentional authoring change is made: run `renma scan . --fail-on high`, fix relevant diagnostics, and rerun the scan.",
|
|
485
|
-
];
|
|
486
|
-
}
|
|
487
|
-
return ["Verification:", "- Run `renma scan . --fail-on high`."];
|
|
488
|
-
}
|
|
489
|
-
function renderSkillSuggestionNextSteps(state) {
|
|
490
|
-
if (state === "blocked") {
|
|
491
|
-
return [
|
|
492
|
-
"Next steps:",
|
|
493
|
-
"1. Review the conflicts or invalid evidence and confirm the Skill's intent using your platform's standard Skill authoring guidance.",
|
|
494
|
-
"2. Do not apply a candidate while Renma cannot generate it safely.",
|
|
495
|
-
"3. Correct the source evidence, then rerun `renma suggest-metadata <SKILL.md>`.",
|
|
496
|
-
"4. After intended corrections, run `renma scan . --fail-on high`, fix relevant diagnostics, and rerun the scan.",
|
|
497
|
-
];
|
|
498
|
-
}
|
|
499
|
-
if (state === "no-proposal") {
|
|
500
|
-
return [
|
|
501
|
-
"Next steps:",
|
|
502
|
-
"1. Review the Skill's trigger description, instructions, workflow, constraints, and completion criteria using your platform's standard Skill authoring guidance.",
|
|
503
|
-
"2. No metadata or migration change is proposed; preserve the existing source.",
|
|
504
|
-
"3. If a separate, intentionally reviewed authoring change is made, run `renma scan . --fail-on high`, fix relevant diagnostics, and rerun the scan.",
|
|
505
|
-
"4. If no separate change is made, stop without manufacturing work.",
|
|
506
|
-
];
|
|
507
|
-
}
|
|
508
|
-
return [
|
|
509
|
-
"Next steps:",
|
|
510
|
-
"1. Review the suggestion; Renma does not edit the Skill automatically.",
|
|
511
|
-
"2. Review the Skill's trigger description, instructions, workflow, constraints, and completion criteria using your platform's standard Skill authoring guidance.",
|
|
512
|
-
"3. Apply only the intended metadata or migration changes.",
|
|
513
|
-
"4. Run `renma scan . --fail-on high`.",
|
|
514
|
-
"5. Fix relevant diagnostics and rerun the scan.",
|
|
515
|
-
];
|
|
516
|
-
}
|
|
517
221
|
function buildInstructions(input) {
|
|
518
222
|
return [
|
|
519
223
|
"Inspect the existing asset before editing.",
|
|
@@ -541,93 +245,6 @@ function ownerInstruction(input) {
|
|
|
541
245
|
}
|
|
542
246
|
return "Do not add owner unless the existing asset already declares one or a maintainer provides one.";
|
|
543
247
|
}
|
|
544
|
-
function metadataLines(candidateMetadata) {
|
|
545
|
-
const entries = Object.entries(candidateMetadata);
|
|
546
|
-
if (entries.length === 0) {
|
|
547
|
-
return ["- (none)"];
|
|
548
|
-
}
|
|
549
|
-
return entries.map(([field, value]) => `- ${field}: \`${value}\``);
|
|
550
|
-
}
|
|
551
|
-
function blockedMetadataLines(blockedMetadata) {
|
|
552
|
-
if (blockedMetadata.length === 0) {
|
|
553
|
-
return ["- (none)"];
|
|
554
|
-
}
|
|
555
|
-
return blockedMetadata.map((item) => `- ${item.field}: ${item.reason}`);
|
|
556
|
-
}
|
|
557
|
-
function skillLocalGovernancePromptLines(suggestion) {
|
|
558
|
-
switch (suggestion.decision.reasonCode) {
|
|
559
|
-
case "skill-local-governance-inherited":
|
|
560
|
-
return [
|
|
561
|
-
"Effective governance is inherited from this one resolved parent Skill.",
|
|
562
|
-
];
|
|
563
|
-
case "skill-local-existing-metadata-preserved":
|
|
564
|
-
return [
|
|
565
|
-
"Existing explicit local governance is preserved and is not relabeled as inherited.",
|
|
566
|
-
];
|
|
567
|
-
case "skill-local-unowned":
|
|
568
|
-
return [
|
|
569
|
-
"The parent is resolved, but no effective owner is declared locally or by the parent.",
|
|
570
|
-
];
|
|
571
|
-
case "explicit-human-provided-override":
|
|
572
|
-
return [
|
|
573
|
-
"The parent is resolved; the local candidate is an explicit human-provided override.",
|
|
574
|
-
];
|
|
575
|
-
default:
|
|
576
|
-
return [
|
|
577
|
-
"Governance provenance remains separate from this structural parent resolution.",
|
|
578
|
-
];
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
function skillDecision(migration) {
|
|
582
|
-
if (migration.blocked.length > 0) {
|
|
583
|
-
return {
|
|
584
|
-
status: "blocked",
|
|
585
|
-
decision: {
|
|
586
|
-
reasonCode: "conflicting-or-incomplete-skill-evidence",
|
|
587
|
-
summary: "Renma cannot safely construct the Agent Skills proposal until the blocked evidence is resolved by a human.",
|
|
588
|
-
},
|
|
589
|
-
};
|
|
590
|
-
}
|
|
591
|
-
if (migration.proposalKind === "none" &&
|
|
592
|
-
migration.sourceFormat === "agent-skills") {
|
|
593
|
-
return {
|
|
594
|
-
status: "no-change-recommended",
|
|
595
|
-
decision: {
|
|
596
|
-
reasonCode: "canonical-agent-skill-no-change",
|
|
597
|
-
summary: "The target is already a canonical Agent Skill and no metadata or migration change is recommended.",
|
|
598
|
-
},
|
|
599
|
-
};
|
|
600
|
-
}
|
|
601
|
-
if (migration.proposalKind === "canonical-metadata-retrofit" &&
|
|
602
|
-
migration.canonicalFrontmatter !== undefined &&
|
|
603
|
-
Object.keys(migration.candidateRenmaMetadata).length > 0) {
|
|
604
|
-
return {
|
|
605
|
-
status: "deterministic",
|
|
606
|
-
decision: {
|
|
607
|
-
reasonCode: "explicit-human-provided-override",
|
|
608
|
-
summary: "The metadata candidate uses the owner explicitly supplied by the human; Renma inferred no owner.",
|
|
609
|
-
},
|
|
610
|
-
};
|
|
611
|
-
}
|
|
612
|
-
return {
|
|
613
|
-
status: "human-confirmation-required",
|
|
614
|
-
decision: {
|
|
615
|
-
reasonCode: "agent-skills-migration-review-required",
|
|
616
|
-
summary: "Renma constructed a deterministic migration candidate, but a human must confirm the intended Skill semantics before applying it.",
|
|
617
|
-
},
|
|
618
|
-
};
|
|
619
|
-
}
|
|
620
|
-
function conflictingOwnerEvidence(existingOwner, explicitOwner) {
|
|
621
|
-
if (!existingOwner || !explicitOwner || existingOwner === explicitOwner) {
|
|
622
|
-
return [];
|
|
623
|
-
}
|
|
624
|
-
return [
|
|
625
|
-
{
|
|
626
|
-
field: "owner",
|
|
627
|
-
reason: `Existing owner "${existingOwner}" differs from explicitly provided owner "${explicitOwner}". Do not change ownership without human review.`,
|
|
628
|
-
},
|
|
629
|
-
];
|
|
630
|
-
}
|
|
631
248
|
function skillLocalInstructions(classification, hasOverride, parentBlocked, hasLocalGovernance) {
|
|
632
249
|
return [
|
|
633
250
|
"Preserve the existing Skill-local file and any explicitly declared supported metadata.",
|
|
@@ -644,9 +261,13 @@ function skillLocalInstructions(classification, hasOverride, parentBlocked, hasL
|
|
|
644
261
|
];
|
|
645
262
|
}
|
|
646
263
|
function suggestionNextActions(targetPath, classification, repositoryRoot) {
|
|
264
|
+
// Do not manufacture an action relative to cwd when the repository root is
|
|
265
|
+
// unresolved; a parent workspace may contain unrelated repositories.
|
|
647
266
|
if (!repositoryRoot)
|
|
648
267
|
return [];
|
|
649
268
|
const actions = [];
|
|
269
|
+
// Verification is anchored to the resolved root, never to `scan .` from the
|
|
270
|
+
// caller's workspace or to a guessed parent directory.
|
|
650
271
|
const scanTarget = repositoryRoot;
|
|
651
272
|
if (classification.parentResolution === "resolved" &&
|
|
652
273
|
classification.parentAssetPath) {
|
|
@@ -694,102 +315,32 @@ function suggestionNextActions(targetPath, classification, repositoryRoot) {
|
|
|
694
315
|
});
|
|
695
316
|
return actions;
|
|
696
317
|
}
|
|
697
|
-
|
|
698
|
-
if (
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
parent: { state: "not-applicable" },
|
|
702
|
-
ownershipSource: "unowned",
|
|
703
|
-
hasLocalPolicyMetadata: false,
|
|
704
|
-
};
|
|
705
|
-
}
|
|
706
|
-
try {
|
|
707
|
-
const snapshot = await collectRepositorySnapshot(repositoryRoot);
|
|
708
|
-
const skillParents = buildSkillParentIndex(snapshot.documents);
|
|
709
|
-
const parent = resolveSkillSupportParent(relativePath, skillParents);
|
|
710
|
-
const resolvedClassification = withResolvedSkillParent(classification, relativePath, skillParents);
|
|
711
|
-
const entry = snapshot.catalog.entries.find((candidate) => candidate.sourcePath === relativePath);
|
|
712
|
-
const policy = collectSecurityPolicyAssetEvidence(snapshot.artifacts, snapshot.config.security).find((candidate) => candidate.path === relativePath);
|
|
713
|
-
return {
|
|
714
|
-
classification: resolvedClassification,
|
|
715
|
-
parent,
|
|
716
|
-
ownershipSource: entry?.ownership.source ??
|
|
717
|
-
(parent.state === "resolved" && parent.parent.owner
|
|
718
|
-
? "inherited"
|
|
719
|
-
: "unowned"),
|
|
720
|
-
hasLocalPolicyMetadata: policy?.hasLocalPolicyMetadata ?? false,
|
|
721
|
-
};
|
|
722
|
-
}
|
|
723
|
-
catch {
|
|
318
|
+
function resolveSkillLocalContext(repository) {
|
|
319
|
+
if (repository.state === "unavailable") {
|
|
320
|
+
// Snapshot failure is unresolved evidence. Keep the command fail-closed
|
|
321
|
+
// instead of treating structural placement as inherited governance.
|
|
724
322
|
return {
|
|
725
|
-
classification,
|
|
323
|
+
classification: repository.classification,
|
|
726
324
|
parent: { state: "not-applicable" },
|
|
727
325
|
ownershipSource: "unowned",
|
|
728
326
|
hasLocalPolicyMetadata: false,
|
|
729
327
|
};
|
|
730
328
|
}
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
: []),
|
|
741
|
-
...(classification.parentAssetPath
|
|
742
|
-
? [`- parent asset: \`${classification.parentAssetPath}\``]
|
|
743
|
-
: []),
|
|
744
|
-
...(classification.parentAssetCandidatePath
|
|
745
|
-
? [
|
|
746
|
-
`- parent candidate: \`${classification.parentAssetCandidatePath}\``,
|
|
747
|
-
`- parent resolution: \`${classification.parentResolution ?? "structural-candidate"}\``,
|
|
748
|
-
]
|
|
749
|
-
: []),
|
|
750
|
-
...(classification.parentAssetCandidates?.length
|
|
751
|
-
? [
|
|
752
|
-
`- parent candidates: ${classification.parentAssetCandidates.map((candidate) => `\`${candidate}\``).join(", ")}`,
|
|
753
|
-
]
|
|
754
|
-
: []),
|
|
755
|
-
...(classification.supportDirectory
|
|
756
|
-
? [`- support directory: \`${classification.supportDirectory}\``]
|
|
757
|
-
: []),
|
|
758
|
-
...(classification.ignoredNestedSegments?.length
|
|
759
|
-
? [
|
|
760
|
-
`- ignored nested segments: ${classification.ignoredNestedSegments.map((segment) => `\`${segment}\``).join(", ")}`,
|
|
761
|
-
]
|
|
762
|
-
: []),
|
|
763
|
-
`- reason: ${classification.reason}`,
|
|
764
|
-
];
|
|
765
|
-
}
|
|
766
|
-
function observedRepositoryFact(classification) {
|
|
767
|
-
if (classification.matchedRule === "skill-local-support") {
|
|
768
|
-
return `The target is under a canonical ${classification.supportDirectory}/ directory within a recognized Skill-root path shape.`;
|
|
769
|
-
}
|
|
770
|
-
if (classification.matchedRule === "context-root" ||
|
|
771
|
-
classification.matchedRule === "context-root-legacy") {
|
|
772
|
-
return `The target is under ${classification.recognizedRoot}/**.`;
|
|
773
|
-
}
|
|
774
|
-
return classification.reason;
|
|
775
|
-
}
|
|
776
|
-
function deterministicInterpretation(classification) {
|
|
777
|
-
if (classification.scope === "skill-local") {
|
|
778
|
-
return classification.parentResolution === "resolved"
|
|
779
|
-
? `The target is structurally Skill-local support and its parent resolves to ${classification.parentAssetPath}; governance provenance is evaluated separately.`
|
|
780
|
-
: "The target is structurally Skill-local support, but inherited governance is unresolved.";
|
|
781
|
-
}
|
|
782
|
-
if (classification.scope === "independent") {
|
|
783
|
-
return `The target is an independently governed ${classification.kind} asset.`;
|
|
784
|
-
}
|
|
785
|
-
if (classification.scope === "repository-support") {
|
|
786
|
-
return "The target is repository support, not an independently governed Context Asset.";
|
|
787
|
-
}
|
|
788
|
-
return "Renma does not classify the target as an independently governed asset.";
|
|
329
|
+
return {
|
|
330
|
+
classification: repository.classification,
|
|
331
|
+
parent: repository.parent,
|
|
332
|
+
ownershipSource: repository.governance?.ownership.source ??
|
|
333
|
+
(repository.parent.state === "resolved" && repository.parent.parent.owner
|
|
334
|
+
? "inherited"
|
|
335
|
+
: "unowned"),
|
|
336
|
+
hasLocalPolicyMetadata: repository.policy?.hasLocalPolicyMetadata ?? false,
|
|
337
|
+
};
|
|
789
338
|
}
|
|
790
339
|
async function pathMigrationCollision(entrypoint, sourceAbsolutePath, repositoryRoot) {
|
|
791
340
|
if (entrypoint.kind === "canonical")
|
|
792
341
|
return undefined;
|
|
342
|
+
// Public migration paths remain repository-relative. Collision I/O must be
|
|
343
|
+
// rebased against the resolved repository root, not process.cwd().
|
|
793
344
|
const targetAbsolutePath = path.resolve(repositoryRoot, entrypoint.targetPath);
|
|
794
345
|
try {
|
|
795
346
|
await lstat(targetAbsolutePath);
|