agent-skillboard 0.2.17 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +62 -0
- package/README.md +128 -260
- package/bin/postinstall.mjs +2 -2
- package/docs/adapters.md +37 -113
- package/docs/ai-skill-routing-goal.md +35 -109
- package/docs/capabilities.md +17 -104
- package/docs/install.md +39 -493
- package/docs/policy-model.md +50 -280
- package/docs/positioning.md +19 -86
- package/docs/profiles.md +21 -153
- package/docs/reference.md +117 -356
- package/docs/rollout-runbook.md +23 -25
- package/docs/routing.md +23 -90
- package/docs/user-flow.md +60 -292
- package/docs/value-proof.md +23 -181
- package/docs/variant-lifecycle.md +47 -67
- package/docs/versioning.md +31 -264
- package/examples/v2-multi-source.config.yaml +35 -0
- package/examples/v2-policy-error.config.yaml +6 -0
- package/package.json +1 -1
- package/src/advisor/actions.mjs +102 -6
- package/src/advisor/application-commands.mjs +10 -9
- package/src/advisor/apply-action.mjs +74 -1
- package/src/advisor/guidance.mjs +24 -16
- package/src/advisor/schema.mjs +17 -6
- package/src/advisor/skills.mjs +18 -5
- package/src/advisor.mjs +27 -9
- package/src/agent-integration-cli.mjs +13 -4
- package/src/agent-integration-content.mjs +22 -12
- package/src/agent-integration-files.mjs +1 -1
- package/src/agent-inventory-platforms.mjs +10 -0
- package/src/agent-inventory.mjs +23 -1
- package/src/agent-skill-import.mjs +2 -2
- package/src/audit-paths.mjs +42 -0
- package/src/brief-cli.mjs +3 -2
- package/src/brief-renderer.mjs +1 -0
- package/src/cli.mjs +398 -127
- package/src/compatibility.mjs +24 -0
- package/src/control/can-use-guard.mjs +21 -1
- package/src/control/config-write.mjs +32 -2
- package/src/control/skill-crud.mjs +5 -0
- package/src/control/v2-guard.mjs +175 -0
- package/src/control/v2-skill-crud.mjs +32 -0
- package/src/control/v2-skill-forget.mjs +38 -0
- package/src/control/variant-status.mjs +47 -1
- package/src/control.mjs +55 -0
- package/src/doctor.mjs +65 -6
- package/src/domain/v2-policy.mjs +111 -0
- package/src/hook-plan.mjs +33 -3
- package/src/impact.mjs +52 -29
- package/src/index.mjs +25 -1
- package/src/init.mjs +50 -34
- package/src/inventory-install-units.mjs +63 -0
- package/src/inventory-json.mjs +279 -0
- package/src/inventory-refresh.mjs +163 -18
- package/src/lifecycle-cli.mjs +40 -12
- package/src/lifecycle-content.mjs +52 -67
- package/src/migration/v1-to-v2.mjs +212 -0
- package/src/migration/v2-files.mjs +211 -0
- package/src/migration/v2-journal.mjs +169 -0
- package/src/migration/v2-projection.mjs +108 -0
- package/src/migration/v2-transaction.mjs +205 -0
- package/src/policy.mjs +3 -0
- package/src/reconcile.mjs +139 -111
- package/src/report.mjs +168 -148
- package/src/review.mjs +2 -0
- package/src/route-advisory.mjs +47 -2
- package/src/route-selection.mjs +38 -2
- package/src/route.mjs +62 -2
- package/src/shared-skill.mjs +301 -0
- package/src/source-digest.mjs +42 -0
- package/src/source-profiles.mjs +27 -0
- package/src/source-verification.mjs +32 -48
- package/src/uninstall.mjs +22 -0
- package/src/user-state-paths.mjs +19 -0
- package/src/user-uninstall.mjs +146 -0
- package/src/workspace.mjs +41 -1
package/src/advisor.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
} from "./control.mjs";
|
|
5
5
|
import { doctorProject } from "./doctor.mjs";
|
|
6
6
|
import { routeSkill } from "./route.mjs";
|
|
7
|
-
import { buildActionCards, buildInitActions } from "./advisor/actions.mjs";
|
|
7
|
+
import { buildActionCards, buildInitActions, buildV2ActionCards } from "./advisor/actions.mjs";
|
|
8
8
|
import {
|
|
9
9
|
buildBrief,
|
|
10
10
|
buildCleanup,
|
|
@@ -25,14 +25,14 @@ import { loadWorkspace } from "./workspace.mjs";
|
|
|
25
25
|
|
|
26
26
|
export async function buildSkillBrief(options = {}) {
|
|
27
27
|
const paths = resolveProjectPaths(options);
|
|
28
|
-
const cleanup = await buildCleanup(paths.root);
|
|
29
28
|
const configDoctor = await doctorProject({
|
|
30
29
|
root: paths.root,
|
|
31
30
|
configPath: paths.configPath,
|
|
32
31
|
skillsRoot: paths.skillsRoot
|
|
33
32
|
});
|
|
34
33
|
|
|
35
|
-
if (!configDoctor.config.exists
|
|
34
|
+
if (!configDoctor.config.exists) {
|
|
35
|
+
const cleanup = userStateCleanup();
|
|
36
36
|
return buildBrief({
|
|
37
37
|
ok: false,
|
|
38
38
|
error: {
|
|
@@ -50,6 +50,7 @@ export async function buildSkillBrief(options = {}) {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
if (!configDoctor.config.valid) {
|
|
53
|
+
const cleanup = userStateCleanup();
|
|
53
54
|
return buildExpectedConfigError(configDoctor, paths, cleanup, {
|
|
54
55
|
code: "invalid-config",
|
|
55
56
|
message: configDoctor.config.error ?? "skillboard.config.yaml is invalid"
|
|
@@ -60,12 +61,15 @@ export async function buildSkillBrief(options = {}) {
|
|
|
60
61
|
try {
|
|
61
62
|
workspace = await loadWorkspace({ configPath: paths.configPath, skillsRoot: paths.skillsRoot });
|
|
62
63
|
} catch (error) {
|
|
64
|
+
const cleanup = userStateCleanup();
|
|
63
65
|
return buildExpectedConfigError(configDoctor, paths, cleanup, {
|
|
64
66
|
code: "invalid-config",
|
|
65
67
|
message: error instanceof Error ? error.message : String(error)
|
|
66
68
|
}, options);
|
|
67
69
|
}
|
|
68
70
|
|
|
71
|
+
const cleanup = workspace.version === 2 ? userStateCleanup() : await buildCleanup(paths.root);
|
|
72
|
+
|
|
69
73
|
const doctor = await doctorProject({
|
|
70
74
|
root: paths.root,
|
|
71
75
|
configPath: paths.configPath,
|
|
@@ -74,13 +78,16 @@ export async function buildSkillBrief(options = {}) {
|
|
|
74
78
|
verifySources: options.verifySources
|
|
75
79
|
});
|
|
76
80
|
const sourceAudit = auditSources(workspace);
|
|
77
|
-
const workflow =
|
|
78
|
-
|
|
81
|
+
const workflow = workspace.version === 2
|
|
82
|
+
? emptyWorkflowState()
|
|
83
|
+
: resolveWorkflow(listWorkflows(workspace), options.workflow);
|
|
84
|
+
const reviewQueue = workspace.version === 2 ? [] : buildReviewQueue(workspace, sourceAudit);
|
|
79
85
|
|
|
80
86
|
if (workflow.unknown) {
|
|
81
87
|
const skills = skillsWithoutWorkflow(workspace);
|
|
82
88
|
const actionData = actionsForBrief({ options, paths, workflow, skills, reviewQueue, cleanup, workspace });
|
|
83
89
|
return buildBrief({
|
|
90
|
+
compatibility: workspace.compatibility,
|
|
84
91
|
ok: false,
|
|
85
92
|
error: {
|
|
86
93
|
code: "unknown-workflow",
|
|
@@ -96,11 +103,13 @@ export async function buildSkillBrief(options = {}) {
|
|
|
96
103
|
});
|
|
97
104
|
}
|
|
98
105
|
|
|
99
|
-
const skills = skillsForWorkflow(workspace, workflow.selected, sourceAudit);
|
|
106
|
+
const skills = skillsForWorkflow(workspace, workflow.selected, sourceAudit, options.agent);
|
|
100
107
|
const actionData = actionsForBrief({ options, paths, workflow, skills, reviewQueue, cleanup, workspace });
|
|
101
108
|
const route = routeForBrief({ options, paths, workflow, workspace });
|
|
102
|
-
const availabilityOk = doctor.config.valid && doctor.policy.ok && doctor.
|
|
109
|
+
const availabilityOk = doctor.config.valid && doctor.policy.ok && doctor.inventory.ok
|
|
110
|
+
&& (workspace.version === 2 || doctor.sources.ok);
|
|
103
111
|
return buildBrief({
|
|
112
|
+
compatibility: workspace.compatibility,
|
|
104
113
|
ok: availabilityOk,
|
|
105
114
|
health: healthForBrief(doctor, paths, availabilityOk),
|
|
106
115
|
workflow,
|
|
@@ -113,14 +122,22 @@ export async function buildSkillBrief(options = {}) {
|
|
|
113
122
|
});
|
|
114
123
|
}
|
|
115
124
|
|
|
125
|
+
function userStateCleanup() {
|
|
126
|
+
return {
|
|
127
|
+
conservative: { dryRun: true, removed: [], updated: [], preserved: [] },
|
|
128
|
+
full_reset: { dryRun: true, removed: [], updated: [], preserved: [] }
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
116
132
|
function routeForBrief({ options, paths, workflow, workspace }) {
|
|
117
133
|
const intent = options.intent?.trim();
|
|
118
|
-
|
|
134
|
+
const workflowRequired = workspace.version !== 2 && workflow.selected === null;
|
|
135
|
+
if (intent === undefined || intent.length === 0 || workflowRequired || workflow.unknown || workflow.needs_selection) {
|
|
119
136
|
return undefined;
|
|
120
137
|
}
|
|
121
138
|
return routeSkill(workspace, {
|
|
122
139
|
intent,
|
|
123
|
-
workflow: workflow.selected,
|
|
140
|
+
...(workspace.version === 2 ? { agent: options.agent } : workflow.selected === null ? {} : { workflow: workflow.selected }),
|
|
124
141
|
configPath: paths.configPath,
|
|
125
142
|
skillsRoot: paths.skillsRoot
|
|
126
143
|
});
|
|
@@ -155,6 +172,7 @@ function actionsForBrief(context) {
|
|
|
155
172
|
if (!requestedActions(context.options)) {
|
|
156
173
|
return { reviewQueue: context.reviewQueue, actions: undefined };
|
|
157
174
|
}
|
|
175
|
+
if (context.workspace.version === 2) return { reviewQueue: [], actions: buildV2ActionCards(context) };
|
|
158
176
|
return buildActionCards(context);
|
|
159
177
|
}
|
|
160
178
|
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { isAbsolute, relative, resolve } from "node:path";
|
|
2
|
+
import { mkdir } from "node:fs/promises";
|
|
2
3
|
import { createInterface } from "node:readline";
|
|
3
4
|
import { installAgentIntegration, uninstallAgentIntegration } from "./agent-integration-files.mjs";
|
|
4
|
-
import { resolveSetupHome, setupOwnership } from "./agent-integration-home.mjs";
|
|
5
|
+
import { applyOwnership, resolveSetupHome, setupOwnership } from "./agent-integration-home.mjs";
|
|
5
6
|
import { setupAgentSkillTargets, supportedAgentNames } from "./agent-skill-roots.mjs";
|
|
7
|
+
import { refreshAgentInventory } from "./inventory-refresh.mjs";
|
|
6
8
|
|
|
7
9
|
export async function runSetupCommand(options, stdout, runtime = defaultRuntime()) {
|
|
8
10
|
if (options.get("dir") !== undefined) {
|
|
@@ -27,15 +29,22 @@ export async function runSetupCommand(options, stdout, runtime = defaultRuntime(
|
|
|
27
29
|
return 1;
|
|
28
30
|
}
|
|
29
31
|
}
|
|
30
|
-
const
|
|
32
|
+
const ownership = setupOwnership(env, runtime, home);
|
|
33
|
+
const result = await installAgentIntegration(targets, ownership);
|
|
34
|
+
await mkdir(home, { recursive: true });
|
|
35
|
+
const inventory = await refreshAgentInventory({ root: home, home, env });
|
|
36
|
+
await applyOwnership(resolve(home, inventory.configPath), ownership);
|
|
37
|
+
if (inventory.inventoryPath !== null) await applyOwnership(resolve(home, inventory.inventoryPath), ownership);
|
|
31
38
|
stdout.write("SkillBoard agent integration installed.\n");
|
|
32
39
|
writeList(stdout, "Created", result.created);
|
|
33
40
|
writeList(stdout, "Updated", result.updated);
|
|
34
41
|
writeList(stdout, "Unchanged", result.unchanged);
|
|
35
42
|
writeList(stdout, "Preserved", result.preserved);
|
|
43
|
+
stdout.write(`User policy: ${inventory.configPath}\n`);
|
|
44
|
+
stdout.write(`Observed skills: ${inventory.scan.scannedSkills}\n`);
|
|
36
45
|
stdout.write("Next:\n");
|
|
37
46
|
stdout.write("- Restart or refresh agents that cache user skills.\n");
|
|
38
|
-
stdout.write("-
|
|
47
|
+
stdout.write("- User-level policy and inventory were refreshed; no project was initialized.\n");
|
|
39
48
|
stdout.write('- Ask the agent in a workspace: "Review this plan and point out weak assumptions."\n');
|
|
40
49
|
stdout.write("- SkillBoard will step in when skills overlap, routing is ambiguous, or you ask for a skill decision.\n");
|
|
41
50
|
return 0;
|
|
@@ -76,7 +85,7 @@ function writeList(stdout, label, values) {
|
|
|
76
85
|
function writeSetupConfirmation(stdout, targets, command) {
|
|
77
86
|
stdout.write("SkillBoard setup installs agent-layer integration, not project files.\n");
|
|
78
87
|
stdout.write("It writes a SkillBoard guidance skill into detected user agent skill roots so agents can resolve skill priority when choices overlap.\n");
|
|
79
|
-
stdout.write("It
|
|
88
|
+
stdout.write("It creates ~/skillboard.config.yaml and ~/.skillboard/inventory.json as one user-level control plane; skillboard init is not needed for normal use.\n");
|
|
80
89
|
stdout.write("Targets:\n");
|
|
81
90
|
for (const target of targets) {
|
|
82
91
|
stdout.write(`- ${target.agent}: ${target.skillPath}\n`);
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
export const AGENT_INTEGRATION_START = "<!-- BEGIN SKILLBOARD AGENT INTEGRATION -->";
|
|
2
2
|
export const AGENT_INTEGRATION_END = "<!-- END SKILLBOARD AGENT INTEGRATION -->";
|
|
3
3
|
|
|
4
|
-
export function agentIntegrationSkill() {
|
|
4
|
+
export function agentIntegrationSkill(agent = "<current-agent>") {
|
|
5
5
|
return `---
|
|
6
6
|
name: skillboard
|
|
7
|
-
description: Use SkillBoard when several installed skills could apply, a skill choice is ambiguous,
|
|
7
|
+
description: Use SkillBoard when several installed skills could apply, a skill choice is ambiguous, or the user explicitly asks which skill should be used, shared, imported, preferred, avoided, or prioritized.
|
|
8
8
|
---
|
|
9
9
|
${AGENT_INTEGRATION_START}
|
|
10
10
|
# SkillBoard Agent Integration
|
|
@@ -13,21 +13,24 @@ Use this skill to let SkillBoard guide skill selection above individual projects
|
|
|
13
13
|
|
|
14
14
|
## Layering
|
|
15
15
|
|
|
16
|
-
- SkillBoard is the user-level control plane for skill priority, overlap resolution, and
|
|
16
|
+
- SkillBoard is the user-level control plane for skill priority, overlap resolution, and opt-in cross-agent sharing.
|
|
17
17
|
- Project management belongs to the agent or workspace layer. Do not initialize, attach, rewrite, or manage a project just because SkillBoard is installed.
|
|
18
|
-
- Package install and \`skillboard setup\`
|
|
19
|
-
- \`skillboard init\` is
|
|
18
|
+
- Package install and \`skillboard setup\` refresh user-agent guidance plus the home policy and inventory.
|
|
19
|
+
- \`skillboard init\` is deprecated project-local policy bootstrap. It is not needed for normal use; use it only when maintaining an existing workspace that intentionally keeps local policy files such as \`skillboard.config.yaml\`, \`.skillboard/\`, \`AGENTS.md\`, or \`CLAUDE.md\`.
|
|
20
20
|
|
|
21
21
|
## Default Behavior
|
|
22
22
|
|
|
23
|
-
- Installed
|
|
24
|
-
-
|
|
23
|
+
- Installed skills stay agent-local by default. Only a skill the user explicitly shares is promoted across supported agents.
|
|
24
|
+
- This integration is running for agent \`${agent}\`; pass \`--agent ${agent}\` to brief, route, can-use, and guard.
|
|
25
|
+
- For ordinary user requests, work normally; invoke SkillBoard only when skill choice is ambiguous, skills overlap, or the user asks for a SkillBoard or skill decision.
|
|
25
26
|
- If the user explicitly asks for a specific installed skill, honor that request when the guard allows it instead of rerouting away solely because other skills also match.
|
|
26
27
|
- Do not ask for permission merely because you selected a skill.
|
|
27
28
|
- When you use a skill, disclose it briefly at the start and completion. If SkillBoard says remembered or configured policy selected this skill while other allowed skills were available, mention that at completion.
|
|
28
29
|
|
|
29
30
|
## Cross-Agent Skill Reuse
|
|
30
31
|
|
|
32
|
+
- When the user wants one installed skill available to all supported agents, confirm that persistent policy change once and run \`skillboard skill share <skill> --json\`.
|
|
33
|
+
- To stop SkillBoard-managed propagation, confirm once and run \`skillboard skill unshare <skill> --json\`. This preserves agent-owned originals and removes only SkillBoard-managed copies.
|
|
31
34
|
- When the user wants to use a skill from another agent, run \`skillboard import-skill --from <source-agent> --to <this-agent> --skill <skill> --json\`.
|
|
32
35
|
- If SkillBoard reports the skill is compatible, install it with \`--yes\` and use the copied target-agent skill.
|
|
33
36
|
- If SkillBoard reports \`needs-adaptation\`, explain the compatibility reasons and ask before changing the skill body for this agent.
|
|
@@ -37,11 +40,18 @@ Use this skill to let SkillBoard guide skill selection above individual projects
|
|
|
37
40
|
## Ambiguity Resolution
|
|
38
41
|
|
|
39
42
|
1. Start from the user's task, not from a pre-task inventory prompt.
|
|
40
|
-
2. Identify candidate skills only when multiple installed skills plausibly match
|
|
41
|
-
3. Prefer the skill whose explicit request, description,
|
|
42
|
-
4.
|
|
43
|
-
5.
|
|
44
|
-
6.
|
|
43
|
+
2. Identify candidate skills only when multiple installed skills plausibly match or a manual skill-control request is present.
|
|
44
|
+
3. Prefer the skill whose explicit request, description, and local instructions most directly match the user's task.
|
|
45
|
+
4. Use \`skillboard brief --agent ${agent} --intent <request> --json\` or \`skillboard route <intent> --agent ${agent} --json\` to break ties.
|
|
46
|
+
5. Run \`skillboard guard use <skill-id> --agent ${agent} --json\` immediately before invoking the selected skill.
|
|
47
|
+
6. If the best choice is still ambiguous or the choice would change persistent policy, ask the user which priority to remember.
|
|
48
|
+
7. Continue with the selected skill; do not stop only because other candidate skills exist.
|
|
49
|
+
|
|
50
|
+
## Removal
|
|
51
|
+
|
|
52
|
+
- If an owning installer removed a skill and inventory refresh reports stale policy, confirm once before running \`skillboard skill forget <skill-id>\`. Forget never deletes skill files and refuses installed or shared skills.
|
|
53
|
+
- When the user wants to remove SkillBoard itself, run \`skillboard uninstall --user --dry-run\`, show the managed cleanup plan, confirm once, then run \`skillboard uninstall --user --yes\`. Package removal comes afterward.
|
|
54
|
+
- User cleanup preserves agent-owned and unmanaged skills while removing marker-owned shared copies, managed guidance, and SkillBoard home state.
|
|
45
55
|
|
|
46
56
|
${AGENT_INTEGRATION_END}
|
|
47
57
|
`;
|
|
@@ -8,8 +8,8 @@ export async function installAgentIntegration(targets, ownership = null) {
|
|
|
8
8
|
const updated = [];
|
|
9
9
|
const unchanged = [];
|
|
10
10
|
const preserved = [];
|
|
11
|
-
const content = agentIntegrationSkill();
|
|
12
11
|
for (const target of targets) {
|
|
12
|
+
const content = agentIntegrationSkill(target.agent);
|
|
13
13
|
if (!await isSafeManagedSkillTarget(target)) {
|
|
14
14
|
preserved.push(`${target.agent}:${target.skillPath}`);
|
|
15
15
|
continue;
|
|
@@ -6,15 +6,21 @@ export async function defaultScanRoots(home, env) {
|
|
|
6
6
|
const agentRoots = await Promise.all([
|
|
7
7
|
agentSkillRootCandidates("codex", home, env),
|
|
8
8
|
agentSkillRootCandidates("claude", home, env),
|
|
9
|
+
agentSkillRootCandidates("opencode", home, env),
|
|
9
10
|
agentSkillRootCandidates("hermes", home, env)
|
|
10
11
|
]);
|
|
11
12
|
return [
|
|
12
13
|
join(codexHome, "skills", ".system"),
|
|
13
14
|
join(codexHome, "plugins", "cache"),
|
|
15
|
+
join(home, ".agents", "shared-skills"),
|
|
14
16
|
...agentRoots.flat().map((root) => root.skillRoot)
|
|
15
17
|
];
|
|
16
18
|
}
|
|
17
19
|
|
|
20
|
+
export function sharedUserUnit(path, home) {
|
|
21
|
+
return userSkillUnit("shared.user-skills", path, home);
|
|
22
|
+
}
|
|
23
|
+
|
|
18
24
|
export function systemCodexUnit(path, home) {
|
|
19
25
|
return {
|
|
20
26
|
id: "codex.system-skills",
|
|
@@ -38,6 +44,10 @@ export function userClaudeUnit(path, home) {
|
|
|
38
44
|
return userSkillUnit("claude.user-skills", path, home);
|
|
39
45
|
}
|
|
40
46
|
|
|
47
|
+
export function userOpenCodeUnit(path, home) {
|
|
48
|
+
return userSkillUnit("opencode.user-skills", path, home);
|
|
49
|
+
}
|
|
50
|
+
|
|
41
51
|
export function userHermesUnit(path, home) {
|
|
42
52
|
return userSkillUnit("hermes.user-skills", path, home);
|
|
43
53
|
}
|
package/src/agent-inventory.mjs
CHANGED
|
@@ -9,14 +9,25 @@ import {
|
|
|
9
9
|
hermesProfileUnit,
|
|
10
10
|
isHermesProfileSkillsPath,
|
|
11
11
|
safeSegment,
|
|
12
|
+
sharedUserUnit,
|
|
12
13
|
systemCodexUnit,
|
|
13
14
|
userClaudeUnit,
|
|
14
15
|
userCodexUnit,
|
|
15
|
-
userHermesUnit
|
|
16
|
+
userHermesUnit,
|
|
17
|
+
userOpenCodeUnit
|
|
16
18
|
} from "./agent-inventory-platforms.mjs";
|
|
17
19
|
import { readString, requireRecord } from "./config-helpers.mjs";
|
|
18
20
|
|
|
19
21
|
export const agentInventoryDetectors = Object.freeze([
|
|
22
|
+
{
|
|
23
|
+
id: "shared-user-skills",
|
|
24
|
+
matches(path) {
|
|
25
|
+
return path.endsWith("/.agents/shared-skills") || path.endsWith("\\.agents\\shared-skills");
|
|
26
|
+
},
|
|
27
|
+
async discover(path, home) {
|
|
28
|
+
return await discoverSkillDirectory(path, sharedUserUnit(path, home), { excludeSystem: true });
|
|
29
|
+
}
|
|
30
|
+
},
|
|
20
31
|
{
|
|
21
32
|
id: "codex-plugin-cache",
|
|
22
33
|
matches(path) {
|
|
@@ -56,6 +67,16 @@ export const agentInventoryDetectors = Object.freeze([
|
|
|
56
67
|
return await discoverSkillDirectory(path, userClaudeUnit(path, home), { excludeSystem: true });
|
|
57
68
|
}
|
|
58
69
|
},
|
|
70
|
+
{
|
|
71
|
+
id: "opencode-user-skills",
|
|
72
|
+
matches(path) {
|
|
73
|
+
const normalized = path.replace(/\\/g, "/");
|
|
74
|
+
return normalized.endsWith("/.config/opencode/skills") || normalized.endsWith("/.opencode/skills");
|
|
75
|
+
},
|
|
76
|
+
async discover(path, home) {
|
|
77
|
+
return await discoverSkillDirectory(path, userOpenCodeUnit(path, home), { excludeSystem: true });
|
|
78
|
+
}
|
|
79
|
+
},
|
|
59
80
|
{
|
|
60
81
|
id: "hermes-user-skills",
|
|
61
82
|
matches(path) {
|
|
@@ -416,6 +437,7 @@ async function inventoryFromGroups(groups, home, initialWarnings = []) {
|
|
|
416
437
|
const skill = {
|
|
417
438
|
id,
|
|
418
439
|
path: sourceAlias.path,
|
|
440
|
+
skillFile: resolve(file),
|
|
419
441
|
status: "quarantined",
|
|
420
442
|
invocation: "blocked",
|
|
421
443
|
exposure: "exported",
|
|
@@ -157,7 +157,7 @@ async function findSourceSkill(options) {
|
|
|
157
157
|
throw new Error(`Source skill not found: ${options.skill} under ${root}`);
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
-
async function findSourceSkillInRoots(options) {
|
|
160
|
+
export async function findSourceSkillInRoots(options) {
|
|
161
161
|
const misses = [];
|
|
162
162
|
for (const root of options.roots) {
|
|
163
163
|
try {
|
|
@@ -200,7 +200,7 @@ function parseSkillFrontmatter(text) {
|
|
|
200
200
|
return parsed !== null && typeof parsed === "object" ? parsed : {};
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
-
function analyzeAgentCompatibility(content, options) {
|
|
203
|
+
export function analyzeAgentCompatibility(content, options) {
|
|
204
204
|
const reasons = [];
|
|
205
205
|
for (const [agent, markers] of Object.entries(AGENT_MARKERS)) {
|
|
206
206
|
if (agent === options.targetAgent) {
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { basename, isAbsolute, relative, resolve } from "node:path";
|
|
3
|
+
|
|
4
|
+
export function expandPortablePath(value, paths) {
|
|
5
|
+
if (value === "${PROJECT}") return paths.rootDir;
|
|
6
|
+
if (value.startsWith("${PROJECT}/")) return resolve(paths.rootDir, value.slice("${PROJECT}/".length));
|
|
7
|
+
if (value === "${HOME}") return homedir();
|
|
8
|
+
if (value.startsWith("${HOME}/")) return resolve(homedir(), value.slice("${HOME}/".length));
|
|
9
|
+
if (value === "<external>" || value.startsWith("<external>/")) return null;
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function portableObservation(value, options) {
|
|
14
|
+
if (value.startsWith("${PROJECT}") || value.startsWith("${HOME}") || value.startsWith("<external>")) return value;
|
|
15
|
+
if (value.startsWith("~/")) return `\${HOME}/${value.slice(2)}`;
|
|
16
|
+
return isAbsolute(value) ? auditPath(value, options) : value;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function auditPath(path, options) {
|
|
20
|
+
const project = relative(options.rootDir, path);
|
|
21
|
+
if (!project.startsWith("..") && !isAbsolute(project)) {
|
|
22
|
+
return project === "" ? "${PROJECT}" : `\${PROJECT}/${project.replace(/\\/g, "/")}`;
|
|
23
|
+
}
|
|
24
|
+
const home = relative(homedir(), path);
|
|
25
|
+
if (!home.startsWith("..") && !isAbsolute(home)) {
|
|
26
|
+
return home === "" ? "${HOME}" : `\${HOME}/${home.replace(/\\/g, "/")}`;
|
|
27
|
+
}
|
|
28
|
+
return `<external>/${basename(path)}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function redactPathError(error, options) {
|
|
32
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
33
|
+
return replaceText(
|
|
34
|
+
replaceText(replaceText(message, options.rootDir, "${PROJECT}"), options.configDir, "${PROJECT}"),
|
|
35
|
+
homedir(),
|
|
36
|
+
"${HOME}"
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function replaceText(value, search, replacement) {
|
|
41
|
+
return value.split(search).join(replacement);
|
|
42
|
+
}
|
package/src/brief-cli.mjs
CHANGED
|
@@ -5,11 +5,12 @@ import { renderSkillBrief } from "./brief-renderer.mjs";
|
|
|
5
5
|
export async function runBriefCommand(options, stdout, paths) {
|
|
6
6
|
const json = options.get("json") === "true";
|
|
7
7
|
const result = await buildSkillBrief({
|
|
8
|
-
root: briefRoot(options),
|
|
8
|
+
root: briefRoot(options) ?? dirname(paths.configPath),
|
|
9
9
|
configPath: paths.configPath,
|
|
10
10
|
skillsRoot: paths.skillsRoot,
|
|
11
11
|
workflow: options.get("workflow"),
|
|
12
12
|
intent: options.get("intent"),
|
|
13
|
+
agent: options.get("agent"),
|
|
13
14
|
includeActions: options.get("include-actions") === "true" || !json
|
|
14
15
|
});
|
|
15
16
|
writeBriefOutput(stdout, result, options);
|
|
@@ -22,7 +23,7 @@ function briefRoot(options) {
|
|
|
22
23
|
return dir;
|
|
23
24
|
}
|
|
24
25
|
const config = options.get("config");
|
|
25
|
-
return config !== undefined && isAbsolute(config) ? dirname(config) :
|
|
26
|
+
return config !== undefined && isAbsolute(config) ? dirname(config) : undefined;
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
function briefExitCode(result) {
|
package/src/brief-renderer.mjs
CHANGED
|
@@ -47,6 +47,7 @@ export function renderSkillBrief(brief, options = {}) {
|
|
|
47
47
|
`Workflow: ${workflowSummary(brief.workflow)}`,
|
|
48
48
|
""
|
|
49
49
|
];
|
|
50
|
+
if (brief.compatibility?.notice !== undefined) lines.push(brief.compatibility.notice, "");
|
|
50
51
|
if (brief.error !== undefined) {
|
|
51
52
|
lines.push(`Error: ${safeText(brief.error.message)}`, "");
|
|
52
53
|
}
|