@alavida/agentpack 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/package.json +1 -1
- package/skills/agentpack-cli/SKILL.md +12 -0
- package/skills/agentpack-cli/references/skill-lifecycle.md +21 -1
- package/src/application/plugins/build-plugin.js +5 -0
- package/src/application/plugins/inspect-plugin-bundle.js +5 -0
- package/src/application/plugins/validate-plugin-bundle.js +5 -0
- package/src/application/skills/inspect-skill.js +5 -0
- package/src/application/skills/list-stale-skills.js +9 -0
- package/src/application/skills/validate-skills.js +5 -0
- package/src/commands/plugin.js +7 -4
- package/src/commands/skills.js +7 -8
- package/src/domain/skills/skill-graph.js +136 -0
- package/src/domain/skills/skill-model.js +187 -0
- package/src/domain/skills/skill-provenance.js +69 -0
- package/src/infrastructure/fs/build-state-repository.js +16 -0
- package/src/infrastructure/fs/install-state-repository.js +16 -0
- package/src/infrastructure/runtime/materialize-skills.js +117 -0
- package/src/infrastructure/runtime/watch-tree.js +44 -0
- package/src/lib/plugins.js +27 -89
- package/src/lib/skills.js +81 -447
package/README.md
CHANGED
|
@@ -251,7 +251,11 @@ This package also ships an agent-facing skill under:
|
|
|
251
251
|
node_modules/@alavida/agentpack/skills/agentpack-cli/SKILL.md
|
|
252
252
|
```
|
|
253
253
|
|
|
254
|
-
|
|
254
|
+
Consumers can use TanStack Intent in their repos to map that shipped skill into their agent workflow so the agent knows how to use `agentpack` correctly.
|
|
255
|
+
|
|
256
|
+
This is recommended if you want downstream coding agents to follow the right `agentpack` lifecycle automatically.
|
|
257
|
+
|
|
258
|
+
It is still optional. You do not need Intent to install or run the CLI itself.
|
|
255
259
|
|
|
256
260
|
## Metadata Policy
|
|
257
261
|
|
package/package.json
CHANGED
|
@@ -62,8 +62,15 @@ Key idea:
|
|
|
62
62
|
- `SKILL.md.requires` is the source of truth
|
|
63
63
|
- `package.json.dependencies` is the compiled mirror
|
|
64
64
|
- `validate` and `dev` sync dependencies automatically
|
|
65
|
+
- `skills validate` records the current source-hash snapshot in `.agentpack/build-state.json`
|
|
65
66
|
- `skills dev` materializes the compiled skill artifact for runtime use
|
|
66
67
|
|
|
68
|
+
Persistence rule:
|
|
69
|
+
|
|
70
|
+
- commit `.agentpack/build-state.json` so stale detection works across GitHub, CI, and teammate machines
|
|
71
|
+
- commit `.agentpack/catalog.json` in authoring repos
|
|
72
|
+
- do not commit `.agentpack/install.json`
|
|
73
|
+
|
|
67
74
|
Runtime notes:
|
|
68
75
|
|
|
69
76
|
- after `skills dev` writes to `.claude/skills/` or `.agents/skills/`, start a fresh agent session if the current one was already running
|
|
@@ -112,6 +119,11 @@ Default flow:
|
|
|
112
119
|
- `agentpack skills stale <skill>`
|
|
113
120
|
- `agentpack skills validate <skill>`
|
|
114
121
|
|
|
122
|
+
Key idea:
|
|
123
|
+
|
|
124
|
+
- `skills stale` compares current source hashes to the last validated snapshot in `.agentpack/build-state.json`
|
|
125
|
+
- if that file is not committed, stale detection will not persist across clones or CI runs
|
|
126
|
+
|
|
115
127
|
## Conceptual Frame
|
|
116
128
|
|
|
117
129
|
When the user is reasoning about the model itself, explain agentpack this way:
|
|
@@ -42,9 +42,15 @@ Use this when the user is creating or changing a packaged skill in the same repo
|
|
|
42
42
|
What each step means:
|
|
43
43
|
|
|
44
44
|
- `inspect` explains what the skill currently is
|
|
45
|
-
- `validate` checks package readiness and source
|
|
45
|
+
- `validate` checks package readiness, source existence, and records the validated source snapshot in `.agentpack/build-state.json`
|
|
46
46
|
- `dev` links the skill into `.claude/skills/` and `.agents/skills/` for local testing
|
|
47
47
|
|
|
48
|
+
Important persistence behavior:
|
|
49
|
+
|
|
50
|
+
- commit `.agentpack/build-state.json` if you want `skills stale` to work across GitHub, CI, and teammate machines
|
|
51
|
+
- commit `.agentpack/catalog.json` in authoring repos
|
|
52
|
+
- do not commit `.agentpack/install.json`
|
|
53
|
+
|
|
48
54
|
Important runtime behavior:
|
|
49
55
|
|
|
50
56
|
- if the agent session was already running before `skills dev`, start a fresh session so the runtime can rescan the linked skill
|
|
@@ -76,3 +82,17 @@ Consumption stage:
|
|
|
76
82
|
- `skills env`
|
|
77
83
|
|
|
78
84
|
Do not substitute one for the other.
|
|
85
|
+
|
|
86
|
+
## Stale Detection Contract
|
|
87
|
+
|
|
88
|
+
`skills stale` is not comparing against memory or local runtime state.
|
|
89
|
+
|
|
90
|
+
It compares current source hashes against the last validated snapshot recorded in `.agentpack/build-state.json`.
|
|
91
|
+
|
|
92
|
+
That means:
|
|
93
|
+
|
|
94
|
+
1. run `agentpack skills validate <skill-dir>`
|
|
95
|
+
2. commit the updated `.agentpack/build-state.json`
|
|
96
|
+
3. later source changes can be detected by `agentpack skills stale`
|
|
97
|
+
|
|
98
|
+
If the build-state file is not committed, stale detection will only work on the machine where validation was last run.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { inspectStaleSkill, listStaleSkills } from '../../lib/skills.js';
|
|
2
|
+
|
|
3
|
+
export function listStaleSkillsUseCase(options) {
|
|
4
|
+
return listStaleSkills(options);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function inspectStaleSkillUseCase(target, options) {
|
|
8
|
+
return inspectStaleSkill(target, options);
|
|
9
|
+
}
|
package/src/commands/plugin.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
import {
|
|
2
|
+
import { buildPluginUseCase } from '../application/plugins/build-plugin.js';
|
|
3
|
+
import { inspectPluginBundleUseCase } from '../application/plugins/inspect-plugin-bundle.js';
|
|
4
|
+
import { validatePluginBundleUseCase } from '../application/plugins/validate-plugin-bundle.js';
|
|
5
|
+
import { startPluginDev } from '../lib/plugins.js';
|
|
3
6
|
import { output } from '../utils/output.js';
|
|
4
7
|
import { EXIT_CODES } from '../utils/errors.js';
|
|
5
8
|
|
|
@@ -13,7 +16,7 @@ export function pluginCommand() {
|
|
|
13
16
|
.argument('<target>', 'Plugin directory path')
|
|
14
17
|
.action((target, opts, command) => {
|
|
15
18
|
const globalOpts = command.optsWithGlobals();
|
|
16
|
-
const result =
|
|
19
|
+
const result = inspectPluginBundleUseCase(target);
|
|
17
20
|
|
|
18
21
|
if (globalOpts.json) {
|
|
19
22
|
output.json(result);
|
|
@@ -76,7 +79,7 @@ export function pluginCommand() {
|
|
|
76
79
|
.argument('<target>', 'Plugin directory path')
|
|
77
80
|
.action((target, opts, command) => {
|
|
78
81
|
const globalOpts = command.optsWithGlobals();
|
|
79
|
-
const result =
|
|
82
|
+
const result = validatePluginBundleUseCase(target);
|
|
80
83
|
|
|
81
84
|
if (globalOpts.json) {
|
|
82
85
|
output.json(result);
|
|
@@ -114,7 +117,7 @@ export function pluginCommand() {
|
|
|
114
117
|
.argument('<target>', 'Plugin directory path')
|
|
115
118
|
.action((target, opts, command) => {
|
|
116
119
|
const globalOpts = command.optsWithGlobals();
|
|
117
|
-
const result =
|
|
120
|
+
const result = buildPluginUseCase(target, { clean: opts.clean });
|
|
118
121
|
|
|
119
122
|
if (globalOpts.json) {
|
|
120
123
|
output.json(result);
|
package/src/commands/skills.js
CHANGED
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
+
import { inspectSkillUseCase } from '../application/skills/inspect-skill.js';
|
|
3
|
+
import { inspectStaleSkillUseCase, listStaleSkillsUseCase } from '../application/skills/list-stale-skills.js';
|
|
4
|
+
import { validateSkillsUseCase } from '../application/skills/validate-skills.js';
|
|
2
5
|
import {
|
|
3
6
|
inspectSkillDependencies,
|
|
4
7
|
inspectMissingSkillDependencies,
|
|
5
8
|
inspectSkillsStatus,
|
|
6
9
|
inspectRegistryConfig,
|
|
7
|
-
inspectSkill,
|
|
8
10
|
inspectSkillsEnv,
|
|
9
|
-
inspectStaleSkill,
|
|
10
11
|
installSkills,
|
|
11
12
|
listOutdatedSkills,
|
|
12
|
-
listStaleSkills,
|
|
13
13
|
resolveInstallTargets,
|
|
14
14
|
startSkillDev,
|
|
15
15
|
unlinkSkill,
|
|
16
16
|
uninstallSkills,
|
|
17
|
-
validateSkills,
|
|
18
17
|
} from '../lib/skills.js';
|
|
19
18
|
import { output } from '../utils/output.js';
|
|
20
19
|
import { EXIT_CODES } from '../utils/errors.js';
|
|
@@ -250,7 +249,7 @@ export function skillsCommand() {
|
|
|
250
249
|
.argument('<target>', 'Skill directory, SKILL.md path, or package name')
|
|
251
250
|
.action((target, opts, command) => {
|
|
252
251
|
const globalOpts = command.optsWithGlobals();
|
|
253
|
-
const result =
|
|
252
|
+
const result = inspectSkillUseCase(target);
|
|
254
253
|
|
|
255
254
|
if (globalOpts.json) {
|
|
256
255
|
output.json(result);
|
|
@@ -291,7 +290,7 @@ export function skillsCommand() {
|
|
|
291
290
|
const globalOpts = command.optsWithGlobals();
|
|
292
291
|
|
|
293
292
|
if (target) {
|
|
294
|
-
const result =
|
|
293
|
+
const result = inspectStaleSkillUseCase(target);
|
|
295
294
|
|
|
296
295
|
if (globalOpts.json) {
|
|
297
296
|
output.json(result);
|
|
@@ -310,7 +309,7 @@ export function skillsCommand() {
|
|
|
310
309
|
return;
|
|
311
310
|
}
|
|
312
311
|
|
|
313
|
-
const results =
|
|
312
|
+
const results = listStaleSkillsUseCase();
|
|
314
313
|
|
|
315
314
|
if (globalOpts.json) {
|
|
316
315
|
output.json({
|
|
@@ -337,7 +336,7 @@ export function skillsCommand() {
|
|
|
337
336
|
.argument('[target]', 'Optional packaged skill directory, SKILL.md path, or package name')
|
|
338
337
|
.action((target, opts, command) => {
|
|
339
338
|
const globalOpts = command.optsWithGlobals();
|
|
340
|
-
const result =
|
|
339
|
+
const result = validateSkillsUseCase(target);
|
|
341
340
|
|
|
342
341
|
if (globalOpts.json) {
|
|
343
342
|
output.json(
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
export function readSkillGraphNode(repoRoot, packageDir, {
|
|
5
|
+
directInstallNames = new Set(),
|
|
6
|
+
parseSkillFrontmatterFile,
|
|
7
|
+
readPackageMetadata,
|
|
8
|
+
findPackageDirByName,
|
|
9
|
+
normalizeDisplayPath,
|
|
10
|
+
} = {}) {
|
|
11
|
+
const skillFile = join(packageDir, 'SKILL.md');
|
|
12
|
+
if (!existsSync(skillFile)) return null;
|
|
13
|
+
|
|
14
|
+
const skillMetadata = parseSkillFrontmatterFile(skillFile);
|
|
15
|
+
const packageMetadata = readPackageMetadata(packageDir);
|
|
16
|
+
if (!packageMetadata.packageName) return null;
|
|
17
|
+
|
|
18
|
+
const dependencyNames = Object.keys(packageMetadata.dependencies || {})
|
|
19
|
+
.filter((dependencyName) => {
|
|
20
|
+
const localPackageDir = findPackageDirByName(repoRoot, dependencyName);
|
|
21
|
+
if (localPackageDir && existsSync(join(localPackageDir, 'SKILL.md'))) return true;
|
|
22
|
+
const installedPackageDir = join(repoRoot, 'node_modules', ...dependencyName.split('/'));
|
|
23
|
+
return existsSync(join(installedPackageDir, 'SKILL.md'));
|
|
24
|
+
})
|
|
25
|
+
.sort((a, b) => a.localeCompare(b));
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
name: skillMetadata.name,
|
|
29
|
+
packageName: packageMetadata.packageName,
|
|
30
|
+
packageVersion: packageMetadata.packageVersion,
|
|
31
|
+
skillPath: normalizeDisplayPath(repoRoot, packageDir),
|
|
32
|
+
skillFile: normalizeDisplayPath(repoRoot, skillFile),
|
|
33
|
+
direct: directInstallNames.has(packageMetadata.packageName),
|
|
34
|
+
dependencies: dependencyNames,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function buildSkillGraph(repoRoot, packageDirs, options) {
|
|
39
|
+
const nodes = new Map();
|
|
40
|
+
|
|
41
|
+
for (const packageDir of packageDirs) {
|
|
42
|
+
const node = readSkillGraphNode(repoRoot, packageDir, options);
|
|
43
|
+
if (!node) continue;
|
|
44
|
+
nodes.set(node.packageName, node);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return nodes;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function buildReverseDependencies(nodes) {
|
|
51
|
+
const reverse = new Map();
|
|
52
|
+
for (const packageName of nodes.keys()) reverse.set(packageName, []);
|
|
53
|
+
|
|
54
|
+
for (const node of nodes.values()) {
|
|
55
|
+
for (const dependencyName of node.dependencies || []) {
|
|
56
|
+
if (!reverse.has(dependencyName)) continue;
|
|
57
|
+
reverse.get(dependencyName).push(node.packageName);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
for (const values of reverse.values()) values.sort((a, b) => a.localeCompare(b));
|
|
62
|
+
return reverse;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function buildSkillStatusMap(nodes, staleSkills = new Set()) {
|
|
66
|
+
const cache = new Map();
|
|
67
|
+
|
|
68
|
+
function resolveStatus(packageName, seen = new Set()) {
|
|
69
|
+
if (cache.has(packageName)) return cache.get(packageName);
|
|
70
|
+
if (staleSkills.has(packageName)) {
|
|
71
|
+
cache.set(packageName, 'stale');
|
|
72
|
+
return 'stale';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (seen.has(packageName)) return 'current';
|
|
76
|
+
seen.add(packageName);
|
|
77
|
+
|
|
78
|
+
const node = nodes.get(packageName);
|
|
79
|
+
if (!node) {
|
|
80
|
+
cache.set(packageName, null);
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const dependencyStatuses = (node.dependencies || [])
|
|
85
|
+
.map((dependencyName) => resolveStatus(dependencyName, new Set(seen)))
|
|
86
|
+
.filter(Boolean);
|
|
87
|
+
|
|
88
|
+
const status = dependencyStatuses.some((value) => value === 'stale' || value === 'affected')
|
|
89
|
+
? 'affected'
|
|
90
|
+
: 'current';
|
|
91
|
+
|
|
92
|
+
cache.set(packageName, status);
|
|
93
|
+
return status;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
for (const packageName of nodes.keys()) {
|
|
97
|
+
resolveStatus(packageName);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return cache;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function readNodeStatus(statusMap, packageName) {
|
|
104
|
+
if (!statusMap) return null;
|
|
105
|
+
return statusMap.get(packageName) || null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function resolveDependencyClosure(initialRequires, { resolveNode }) {
|
|
109
|
+
const seen = new Set();
|
|
110
|
+
const queue = [...initialRequires];
|
|
111
|
+
const resolved = [];
|
|
112
|
+
const unresolved = [];
|
|
113
|
+
|
|
114
|
+
while (queue.length > 0) {
|
|
115
|
+
const packageName = queue.shift();
|
|
116
|
+
if (seen.has(packageName)) continue;
|
|
117
|
+
seen.add(packageName);
|
|
118
|
+
|
|
119
|
+
const node = resolveNode(packageName);
|
|
120
|
+
if (!node) {
|
|
121
|
+
unresolved.push(packageName);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
resolved.push(node);
|
|
126
|
+
|
|
127
|
+
for (const requirement of node.requires || []) {
|
|
128
|
+
if (!seen.has(requirement)) queue.push(requirement);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
resolved.sort((a, b) => a.packageName.localeCompare(b.packageName));
|
|
133
|
+
unresolved.sort((a, b) => a.localeCompare(b));
|
|
134
|
+
|
|
135
|
+
return { resolved, unresolved };
|
|
136
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join, relative } from 'node:path';
|
|
3
|
+
import { NotFoundError, ValidationError } from '../../utils/errors.js';
|
|
4
|
+
|
|
5
|
+
function parseScalar(value) {
|
|
6
|
+
const trimmed = value.trim();
|
|
7
|
+
if (
|
|
8
|
+
(trimmed.startsWith('"') && trimmed.endsWith('"')) ||
|
|
9
|
+
(trimmed.startsWith("'") && trimmed.endsWith("'"))
|
|
10
|
+
) {
|
|
11
|
+
return trimmed.slice(1, -1);
|
|
12
|
+
}
|
|
13
|
+
return trimmed;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function foldBlockScalar(lines, startIndex, baseIndent) {
|
|
17
|
+
const values = [];
|
|
18
|
+
let index = startIndex + 1;
|
|
19
|
+
|
|
20
|
+
while (index < lines.length) {
|
|
21
|
+
const rawLine = lines[index];
|
|
22
|
+
if (!rawLine.trim()) {
|
|
23
|
+
values.push('');
|
|
24
|
+
index += 1;
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const indentMatch = rawLine.match(/^(\s*)/);
|
|
29
|
+
const indent = indentMatch ? indentMatch[1].length : 0;
|
|
30
|
+
if (indent <= baseIndent) break;
|
|
31
|
+
|
|
32
|
+
values.push(rawLine.slice(baseIndent + 2).trimEnd());
|
|
33
|
+
index += 1;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const folded = values
|
|
37
|
+
.join('\n')
|
|
38
|
+
.split('\n\n')
|
|
39
|
+
.map((chunk) => chunk.split('\n').join(' ').trim())
|
|
40
|
+
.filter((chunk, idx, arr) => chunk.length > 0 || idx < arr.length - 1)
|
|
41
|
+
.join('\n\n')
|
|
42
|
+
.trim();
|
|
43
|
+
|
|
44
|
+
return { value: folded, nextIndex: index };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function ensureContainer(target, key) {
|
|
48
|
+
if (!target[key] || typeof target[key] !== 'object' || Array.isArray(target[key])) {
|
|
49
|
+
target[key] = {};
|
|
50
|
+
}
|
|
51
|
+
return target[key];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function parseSkillFrontmatterFile(skillFilePath) {
|
|
55
|
+
if (!existsSync(skillFilePath)) {
|
|
56
|
+
throw new NotFoundError(`skill file not found: ${skillFilePath}`, { code: 'skill_not_found' });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const content = readFileSync(skillFilePath, 'utf-8');
|
|
60
|
+
if (!content.startsWith('---\n')) {
|
|
61
|
+
throw new ValidationError('SKILL.md missing frontmatter', { code: 'missing_frontmatter' });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const fmEnd = content.indexOf('\n---', 4);
|
|
65
|
+
if (fmEnd === -1) {
|
|
66
|
+
throw new ValidationError('SKILL.md has unclosed frontmatter', { code: 'unclosed_frontmatter' });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const lines = content.slice(4, fmEnd).split('\n');
|
|
70
|
+
const fields = {};
|
|
71
|
+
let activeArrayKey = null;
|
|
72
|
+
let activeArrayTarget = null;
|
|
73
|
+
let activeParentKey = null;
|
|
74
|
+
|
|
75
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
76
|
+
const rawLine = lines[index];
|
|
77
|
+
const line = rawLine.trimEnd();
|
|
78
|
+
if (!line.trim()) continue;
|
|
79
|
+
|
|
80
|
+
const listMatch = rawLine.match(/^(\s*)-\s+(.+)$/);
|
|
81
|
+
if (listMatch && activeArrayKey && activeArrayTarget) {
|
|
82
|
+
activeArrayTarget[activeArrayKey].push(parseScalar(listMatch[2]));
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const nestedKeyMatch = rawLine.match(/^\s{2}([A-Za-z][\w-]*):\s*(.*)$/);
|
|
87
|
+
if (nestedKeyMatch && activeParentKey) {
|
|
88
|
+
const [, key, value] = nestedKeyMatch;
|
|
89
|
+
const parent = ensureContainer(fields, activeParentKey);
|
|
90
|
+
if (value === '') {
|
|
91
|
+
parent[key] = [];
|
|
92
|
+
activeArrayKey = key;
|
|
93
|
+
activeArrayTarget = parent;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
parent[key] = parseScalar(value);
|
|
98
|
+
activeArrayKey = null;
|
|
99
|
+
activeArrayTarget = null;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const keyMatch = rawLine.match(/^([A-Za-z][\w-]*):\s*(.*)$/);
|
|
104
|
+
if (!keyMatch) continue;
|
|
105
|
+
|
|
106
|
+
const [, key, value] = keyMatch;
|
|
107
|
+
if (value === '>' || value === '|') {
|
|
108
|
+
const { value: blockValue, nextIndex } = foldBlockScalar(lines, index, 0);
|
|
109
|
+
fields[key] = blockValue;
|
|
110
|
+
activeParentKey = null;
|
|
111
|
+
activeArrayKey = null;
|
|
112
|
+
activeArrayTarget = null;
|
|
113
|
+
index = nextIndex - 1;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (value === '') {
|
|
118
|
+
fields[key] = fields[key] && typeof fields[key] === 'object' && !Array.isArray(fields[key])
|
|
119
|
+
? fields[key]
|
|
120
|
+
: [];
|
|
121
|
+
activeParentKey = key;
|
|
122
|
+
activeArrayKey = Array.isArray(fields[key]) ? key : null;
|
|
123
|
+
activeArrayTarget = Array.isArray(fields[key]) ? fields : null;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
fields[key] = parseScalar(value);
|
|
128
|
+
activeParentKey = null;
|
|
129
|
+
activeArrayKey = null;
|
|
130
|
+
activeArrayTarget = null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (!fields.name) {
|
|
134
|
+
throw new ValidationError('SKILL.md frontmatter missing "name" field', { code: 'missing_name' });
|
|
135
|
+
}
|
|
136
|
+
if (!fields.description) {
|
|
137
|
+
throw new ValidationError('SKILL.md frontmatter missing "description" field', { code: 'missing_description' });
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
name: fields.name,
|
|
142
|
+
description: fields.description,
|
|
143
|
+
sources: Array.isArray(fields.metadata?.sources)
|
|
144
|
+
? fields.metadata.sources
|
|
145
|
+
: (Array.isArray(fields.sources) ? fields.sources : []),
|
|
146
|
+
requires: Array.isArray(fields.metadata?.requires)
|
|
147
|
+
? fields.metadata.requires
|
|
148
|
+
: (Array.isArray(fields.requires) ? fields.requires : []),
|
|
149
|
+
status: typeof fields.metadata?.status === 'string' ? fields.metadata.status : null,
|
|
150
|
+
replacement: typeof fields.metadata?.replacement === 'string' ? fields.metadata.replacement : null,
|
|
151
|
+
message: typeof fields.metadata?.message === 'string' ? fields.metadata.message : null,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function normalizeDisplayPath(repoRoot, absolutePath) {
|
|
156
|
+
return relative(repoRoot, absolutePath).split('\\').join('/');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function normalizeRepoPath(repoRoot, absolutePath) {
|
|
160
|
+
return normalizeDisplayPath(repoRoot, absolutePath);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function readPackageMetadata(packageDir) {
|
|
164
|
+
const packageJsonPath = join(packageDir, 'package.json');
|
|
165
|
+
if (!existsSync(packageJsonPath)) {
|
|
166
|
+
return {
|
|
167
|
+
packageName: null,
|
|
168
|
+
packageVersion: null,
|
|
169
|
+
dependencies: {},
|
|
170
|
+
devDependencies: {},
|
|
171
|
+
files: null,
|
|
172
|
+
repository: null,
|
|
173
|
+
publishConfigRegistry: null,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
178
|
+
return {
|
|
179
|
+
packageName: pkg.name || null,
|
|
180
|
+
packageVersion: pkg.version || null,
|
|
181
|
+
dependencies: pkg.dependencies || {},
|
|
182
|
+
devDependencies: pkg.devDependencies || {},
|
|
183
|
+
files: Array.isArray(pkg.files) ? pkg.files : null,
|
|
184
|
+
repository: pkg.repository || null,
|
|
185
|
+
publishConfigRegistry: pkg.publishConfig?.registry || null,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import {
|
|
5
|
+
readBuildState as readBuildStateRecord,
|
|
6
|
+
writeBuildState as writeBuildStateRecord,
|
|
7
|
+
} from '../../infrastructure/fs/build-state-repository.js';
|
|
8
|
+
|
|
9
|
+
export function hashFile(filePath) {
|
|
10
|
+
const digest = createHash('sha256').update(readFileSync(filePath)).digest('hex');
|
|
11
|
+
return `sha256:${digest}`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function readBuildState(repoRoot) {
|
|
15
|
+
return readBuildStateRecord(repoRoot);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function writeBuildState(repoRoot, state) {
|
|
19
|
+
writeBuildStateRecord(repoRoot, state);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function compareRecordedSources(repoRoot, record) {
|
|
23
|
+
const changes = [];
|
|
24
|
+
const recordedSources = record.sources || {};
|
|
25
|
+
|
|
26
|
+
for (const [sourcePath, sourceRecord] of Object.entries(recordedSources)) {
|
|
27
|
+
const absoluteSourcePath = join(repoRoot, sourcePath);
|
|
28
|
+
const currentHash = hashFile(absoluteSourcePath);
|
|
29
|
+
const recordedHash = sourceRecord.hash;
|
|
30
|
+
|
|
31
|
+
if (currentHash !== recordedHash) {
|
|
32
|
+
changes.push({
|
|
33
|
+
path: sourcePath,
|
|
34
|
+
recorded: recordedHash,
|
|
35
|
+
current: currentHash,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return changes;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function buildStateRecordForPackageDir(repoRoot, packageDir, {
|
|
44
|
+
parseSkillFrontmatterFile,
|
|
45
|
+
readPackageMetadata,
|
|
46
|
+
normalizeDisplayPath,
|
|
47
|
+
} = {}) {
|
|
48
|
+
const skillFile = join(packageDir, 'SKILL.md');
|
|
49
|
+
const metadata = parseSkillFrontmatterFile(skillFile);
|
|
50
|
+
const packageMetadata = readPackageMetadata(packageDir);
|
|
51
|
+
const sources = {};
|
|
52
|
+
|
|
53
|
+
for (const sourcePath of metadata.sources) {
|
|
54
|
+
sources[sourcePath] = {
|
|
55
|
+
hash: hashFile(join(repoRoot, sourcePath)),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
packageName: packageMetadata.packageName,
|
|
61
|
+
record: {
|
|
62
|
+
package_version: packageMetadata.packageVersion,
|
|
63
|
+
skill_path: normalizeDisplayPath(repoRoot, packageDir),
|
|
64
|
+
skill_file: normalizeDisplayPath(repoRoot, skillFile),
|
|
65
|
+
sources,
|
|
66
|
+
requires: metadata.requires,
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
export function readBuildState(repoRoot) {
|
|
5
|
+
const buildStatePath = join(repoRoot, '.agentpack', 'build-state.json');
|
|
6
|
+
if (!existsSync(buildStatePath)) {
|
|
7
|
+
return { version: 1, skills: {} };
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return JSON.parse(readFileSync(buildStatePath, 'utf-8'));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function writeBuildState(repoRoot, state) {
|
|
14
|
+
mkdirSync(join(repoRoot, '.agentpack'), { recursive: true });
|
|
15
|
+
writeFileSync(join(repoRoot, '.agentpack', 'build-state.json'), JSON.stringify(state, null, 2) + '\n');
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
export function readInstallState(repoRoot) {
|
|
5
|
+
const installStatePath = join(repoRoot, '.agentpack', 'install.json');
|
|
6
|
+
if (!existsSync(installStatePath)) {
|
|
7
|
+
return { version: 1, installs: {} };
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return JSON.parse(readFileSync(installStatePath, 'utf-8'));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function writeInstallState(repoRoot, state) {
|
|
14
|
+
mkdirSync(join(repoRoot, '.agentpack'), { recursive: true });
|
|
15
|
+
writeFileSync(join(repoRoot, '.agentpack', 'install.json'), JSON.stringify(state, null, 2) + '\n');
|
|
16
|
+
}
|