agentic-workflow-manager 2.0.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/README.md +62 -0
- package/dist/src/commands/doctor.js +77 -0
- package/dist/src/commands/hooks/index.js +118 -0
- package/dist/src/commands/hooks/install.js +113 -0
- package/dist/src/commands/hooks/resync.js +50 -0
- package/dist/src/commands/hooks/status.js +74 -0
- package/dist/src/commands/hooks/uninstall.js +59 -0
- package/dist/src/commands/init.js +181 -0
- package/dist/src/commands/ledger/index.js +73 -0
- package/dist/src/commands/pin.js +75 -0
- package/dist/src/commands/registry/add.js +62 -0
- package/dist/src/commands/registry/index.js +166 -0
- package/dist/src/commands/registry/install-bundles.js +36 -0
- package/dist/src/commands/registry/remove.js +18 -0
- package/dist/src/commands/registry/status.js +31 -0
- package/dist/src/commands/sensors/baseline.js +93 -0
- package/dist/src/commands/sensors/formatters/eslint.js +29 -0
- package/dist/src/commands/sensors/formatters/generic.js +8 -0
- package/dist/src/commands/sensors/formatters/semgrep.js +18 -0
- package/dist/src/commands/sensors/formatters/test.js +12 -0
- package/dist/src/commands/sensors/formatters/tsc.js +23 -0
- package/dist/src/commands/sensors/index.js +94 -0
- package/dist/src/commands/sensors/init.js +112 -0
- package/dist/src/commands/sensors/install.js +78 -0
- package/dist/src/commands/sensors/run.js +217 -0
- package/dist/src/commands/sensors/status.js +85 -0
- package/dist/src/commands/sensors/types.js +2 -0
- package/dist/src/core/bundle-install.js +116 -0
- package/dist/src/core/bundles.js +122 -0
- package/dist/src/core/cli-version.js +30 -0
- package/dist/src/core/context/materializer.js +30 -0
- package/dist/src/core/context/orchestrator.js +75 -0
- package/dist/src/core/context/project-constitution-inject.js +47 -0
- package/dist/src/core/context/provider.js +29 -0
- package/dist/src/core/context/regenerate.js +61 -0
- package/dist/src/core/context/strategies/config-instructions.js +73 -0
- package/dist/src/core/context/strategies/hook-merge.js +23 -0
- package/dist/src/core/context/strategies/strategy.js +2 -0
- package/dist/src/core/context/types.js +2 -0
- package/dist/src/core/diagnostics/checks.js +141 -0
- package/dist/src/core/diagnostics/context.js +181 -0
- package/dist/src/core/diagnostics/types.js +2 -0
- package/dist/src/core/discovery.js +135 -0
- package/dist/src/core/executor.js +41 -0
- package/dist/src/core/init/detector.js +67 -0
- package/dist/src/core/init/orchestrator.js +54 -0
- package/dist/src/core/init/steps.js +279 -0
- package/dist/src/core/init/types.js +2 -0
- package/dist/src/core/ledger/store.js +73 -0
- package/dist/src/core/ledger/types.js +2 -0
- package/dist/src/core/miro.js +314 -0
- package/dist/src/core/profile-pins.js +36 -0
- package/dist/src/core/profile.js +146 -0
- package/dist/src/core/registries.js +205 -0
- package/dist/src/core/registry.js +28 -0
- package/dist/src/core/skill-integrity.js +97 -0
- package/dist/src/core/story-map-parser.js +83 -0
- package/dist/src/core/update-check-worker.js +10 -0
- package/dist/src/core/update-check.js +106 -0
- package/dist/src/core/versioning.js +112 -0
- package/dist/src/index.js +689 -0
- package/dist/src/providers/index.js +63 -0
- package/dist/src/utils/config.js +35 -0
- package/dist/src/utils/grouping.js +51 -0
- package/dist/src/utils/registry-view.js +154 -0
- package/dist/tests/commands/doctor.test.js +98 -0
- package/dist/tests/commands/hooks/install.test.js +149 -0
- package/dist/tests/commands/hooks/resync.test.js +135 -0
- package/dist/tests/commands/hooks/router.test.js +26 -0
- package/dist/tests/commands/hooks/status.test.js +88 -0
- package/dist/tests/commands/hooks/uninstall.test.js +104 -0
- package/dist/tests/commands/init.test.js +87 -0
- package/dist/tests/commands/ledger/index.test.js +64 -0
- package/dist/tests/commands/pin.test.js +72 -0
- package/dist/tests/commands/registry/add.test.js +223 -0
- package/dist/tests/commands/registry/install-bundles.test.js +87 -0
- package/dist/tests/commands/registry/remove.test.js +49 -0
- package/dist/tests/commands/registry/status.test.js +43 -0
- package/dist/tests/commands/sensors/baseline.test.js +106 -0
- package/dist/tests/commands/sensors/formatters/eslint.test.js +32 -0
- package/dist/tests/commands/sensors/formatters/semgrep.test.js +38 -0
- package/dist/tests/commands/sensors/formatters/tsc.test.js +25 -0
- package/dist/tests/commands/sensors/index.test.js +18 -0
- package/dist/tests/commands/sensors/init.test.js +137 -0
- package/dist/tests/commands/sensors/install.test.js +60 -0
- package/dist/tests/commands/sensors/router.test.js +23 -0
- package/dist/tests/commands/sensors/run.test.js +353 -0
- package/dist/tests/commands/sensors/status.test.js +98 -0
- package/dist/tests/core/base-remote.test.js +70 -0
- package/dist/tests/core/bundle-install.test.js +198 -0
- package/dist/tests/core/bundles-multiroot.test.js +68 -0
- package/dist/tests/core/bundles-overrides.test.js +49 -0
- package/dist/tests/core/bundles.test.js +96 -0
- package/dist/tests/core/cli-version.test.js +17 -0
- package/dist/tests/core/context/materializer.test.js +46 -0
- package/dist/tests/core/context/orchestrator.test.js +127 -0
- package/dist/tests/core/context/project-constitution-inject.test.js +82 -0
- package/dist/tests/core/context/provider.test.js +45 -0
- package/dist/tests/core/context/regenerate.test.js +106 -0
- package/dist/tests/core/context/strategies/config-instructions.test.js +88 -0
- package/dist/tests/core/context/strategies/hook-merge.test.js +71 -0
- package/dist/tests/core/context/types.test.js +15 -0
- package/dist/tests/core/diagnostics/checks.test.js +208 -0
- package/dist/tests/core/diagnostics/context.test.js +170 -0
- package/dist/tests/core/discovery-multiroot.test.js +63 -0
- package/dist/tests/core/discovery-overrides.test.js +105 -0
- package/dist/tests/core/discovery.test.js +113 -0
- package/dist/tests/core/executor.test.js +44 -0
- package/dist/tests/core/init/detector.test.js +56 -0
- package/dist/tests/core/init/orchestrator.test.js +122 -0
- package/dist/tests/core/init/steps.test.js +363 -0
- package/dist/tests/core/ledger/gitignore.test.js +13 -0
- package/dist/tests/core/ledger/store.test.js +122 -0
- package/dist/tests/core/miro-layout.test.js +150 -0
- package/dist/tests/core/profile-pins.test.js +131 -0
- package/dist/tests/core/profile-registries.test.js +59 -0
- package/dist/tests/core/profile.test.js +110 -0
- package/dist/tests/core/registries-capability.test.js +52 -0
- package/dist/tests/core/registries-seed.test.js +66 -0
- package/dist/tests/core/registries-sync.test.js +205 -0
- package/dist/tests/core/registries.test.js +91 -0
- package/dist/tests/core/registry-manifest.test.js +95 -0
- package/dist/tests/core/registry-versioned-sync.test.js +113 -0
- package/dist/tests/core/registry.test.js +51 -0
- package/dist/tests/core/skill-integrity.test.js +126 -0
- package/dist/tests/core/story-map-parser.test.js +136 -0
- package/dist/tests/core/sync-gates.test.js +97 -0
- package/dist/tests/core/update-check.test.js +106 -0
- package/dist/tests/core/versioning.test.js +169 -0
- package/dist/tests/integration/pack-e2e.test.js +50 -0
- package/dist/tests/providers/hooks-config.test.js +45 -0
- package/dist/tests/providers/index.test.js +58 -0
- package/dist/tests/providers/injection-config.test.js +25 -0
- package/dist/tests/registry/b3-ledger-wiring.test.js +74 -0
- package/dist/tests/registry/catalog-consistency.test.js +47 -0
- package/dist/tests/registry/design-skills.test.js +146 -0
- package/dist/tests/registry/prose-agnostic.test.js +18 -0
- package/dist/tests/registry/sensor-packs.test.js +45 -0
- package/dist/tests/registry/skill-versions.test.js +26 -0
- package/dist/tests/registry/using-awm.test.js +39 -0
- package/dist/tests/utils/config.test.js +31 -0
- package/dist/tests/utils/grouping.test.js +43 -0
- package/dist/tests/utils/registry-view-overrides.test.js +41 -0
- package/dist/tests/utils/registry-view.test.js +156 -0
- package/package.json +49 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.readArtifactDescription = readArtifactDescription;
|
|
7
|
+
exports.discoverSkills = discoverSkills;
|
|
8
|
+
exports.discoverWorkflows = discoverWorkflows;
|
|
9
|
+
exports.discoverAgents = discoverAgents;
|
|
10
|
+
// src/core/discovery.ts
|
|
11
|
+
const fs_1 = __importDefault(require("fs"));
|
|
12
|
+
const path_1 = __importDefault(require("path"));
|
|
13
|
+
const registries_1 = require("./registries");
|
|
14
|
+
function readArtifactDescription(filePath) {
|
|
15
|
+
try {
|
|
16
|
+
const raw = fs_1.default.readFileSync(filePath, 'utf-8');
|
|
17
|
+
const fmMatch = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
18
|
+
if (!fmMatch)
|
|
19
|
+
return '';
|
|
20
|
+
const line = fmMatch[1]
|
|
21
|
+
.split(/\r?\n/)
|
|
22
|
+
.find((l) => /^description\s*:/.test(l));
|
|
23
|
+
if (!line)
|
|
24
|
+
return '';
|
|
25
|
+
let val = line.replace(/^description\s*:/, '').trim();
|
|
26
|
+
if ((val.startsWith('"') && val.endsWith('"')) ||
|
|
27
|
+
(val.startsWith("'") && val.endsWith("'"))) {
|
|
28
|
+
val = val.slice(1, -1);
|
|
29
|
+
}
|
|
30
|
+
const BLOCK_INDICATORS = new Set(['>-', '>', '|-', '|', '>+', '|+']);
|
|
31
|
+
if (BLOCK_INDICATORS.has(val.trim()))
|
|
32
|
+
return '';
|
|
33
|
+
return val.trim();
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return '';
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function collisionError(kind, name, first, second) {
|
|
40
|
+
return new Error(`Artifact name collision: ${kind} "${name}" exists in both ${first} and ${second}. ` +
|
|
41
|
+
`Remove or rename one of them, or declare "${name}" in "overrides" of the later registry's awm-registry.json.`);
|
|
42
|
+
}
|
|
43
|
+
/** Inserta o resuelve colisión: override declarado en el root posterior → reemplaza
|
|
44
|
+
* (Map.set sobre key existente conserva la posición de inserción); no declarado → error. */
|
|
45
|
+
function mergeEntry(kind, byName, entry, rootOverrides) {
|
|
46
|
+
const prev = byName.get(entry.name);
|
|
47
|
+
if (!prev) {
|
|
48
|
+
byName.set(entry.name, entry);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (rootOverrides.has(entry.name)) {
|
|
52
|
+
byName.set(entry.name, { ...entry, overrode: prev.path });
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
throw collisionError(kind, entry.name, prev.path, entry.path);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Scans skills directories across all provided content roots and returns all valid skills.
|
|
59
|
+
* A valid skill is a directory that contains a SKILL.md file.
|
|
60
|
+
* Throws on name collision across roots unless the later root declares the name in its awm-registry.json overrides.
|
|
61
|
+
*/
|
|
62
|
+
function discoverSkills(roots = (0, registries_1.contentRoots)()) {
|
|
63
|
+
const byName = new Map();
|
|
64
|
+
for (const root of roots) {
|
|
65
|
+
const dir = path_1.default.join(root, 'skills');
|
|
66
|
+
if (!fs_1.default.existsSync(dir))
|
|
67
|
+
continue;
|
|
68
|
+
const overrides = (0, registries_1.readRegistryManifest)(root).overrides;
|
|
69
|
+
for (const entry of fs_1.default.readdirSync(dir, { withFileTypes: true })) {
|
|
70
|
+
if (!entry.isDirectory())
|
|
71
|
+
continue;
|
|
72
|
+
const skillPath = path_1.default.join(dir, entry.name);
|
|
73
|
+
if (!fs_1.default.existsSync(path_1.default.join(skillPath, 'SKILL.md')))
|
|
74
|
+
continue;
|
|
75
|
+
mergeEntry('skill', byName, {
|
|
76
|
+
name: entry.name,
|
|
77
|
+
path: skillPath,
|
|
78
|
+
description: readArtifactDescription(path_1.default.join(skillPath, 'SKILL.md')),
|
|
79
|
+
}, overrides);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return Array.from(byName.values());
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Scans workflows directories across all provided content roots and returns all valid workflows.
|
|
86
|
+
* A valid workflow is a .md file.
|
|
87
|
+
* Throws on name collision across roots unless the later root declares the name in its awm-registry.json overrides.
|
|
88
|
+
*/
|
|
89
|
+
function discoverWorkflows(roots = (0, registries_1.contentRoots)()) {
|
|
90
|
+
const byName = new Map();
|
|
91
|
+
for (const root of roots) {
|
|
92
|
+
const dir = path_1.default.join(root, 'workflows');
|
|
93
|
+
if (!fs_1.default.existsSync(dir))
|
|
94
|
+
continue;
|
|
95
|
+
const overrides = (0, registries_1.readRegistryManifest)(root).overrides;
|
|
96
|
+
for (const entry of fs_1.default.readdirSync(dir, { withFileTypes: true })) {
|
|
97
|
+
if (entry.isDirectory() || !entry.name.endsWith('.md'))
|
|
98
|
+
continue;
|
|
99
|
+
const name = entry.name.replace('.md', '');
|
|
100
|
+
const filePath = path_1.default.join(dir, entry.name);
|
|
101
|
+
mergeEntry('workflow', byName, {
|
|
102
|
+
name,
|
|
103
|
+
path: filePath,
|
|
104
|
+
description: readArtifactDescription(filePath),
|
|
105
|
+
}, overrides);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return Array.from(byName.values());
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Scans agents directories across all provided content roots and returns all valid agent profiles.
|
|
112
|
+
* A valid agent is a .md file.
|
|
113
|
+
* Throws on name collision across roots unless the later root declares the name in its awm-registry.json overrides.
|
|
114
|
+
*/
|
|
115
|
+
function discoverAgents(roots = (0, registries_1.contentRoots)()) {
|
|
116
|
+
const byName = new Map();
|
|
117
|
+
for (const root of roots) {
|
|
118
|
+
const dir = path_1.default.join(root, 'agents');
|
|
119
|
+
if (!fs_1.default.existsSync(dir))
|
|
120
|
+
continue;
|
|
121
|
+
const overrides = (0, registries_1.readRegistryManifest)(root).overrides;
|
|
122
|
+
for (const entry of fs_1.default.readdirSync(dir, { withFileTypes: true })) {
|
|
123
|
+
if (entry.isDirectory() || !entry.name.endsWith('.md'))
|
|
124
|
+
continue;
|
|
125
|
+
const name = entry.name.replace('.md', '');
|
|
126
|
+
const filePath = path_1.default.join(dir, entry.name);
|
|
127
|
+
mergeEntry('agent', byName, {
|
|
128
|
+
name,
|
|
129
|
+
path: filePath,
|
|
130
|
+
description: readArtifactDescription(filePath),
|
|
131
|
+
}, overrides);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return Array.from(byName.values());
|
|
135
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.removeArtifact = removeArtifact;
|
|
7
|
+
exports.installArtifact = installArtifact;
|
|
8
|
+
// src/core/executor.ts
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
function removeArtifact(targetPath) {
|
|
12
|
+
let exists = false;
|
|
13
|
+
try {
|
|
14
|
+
fs_1.default.lstatSync(targetPath);
|
|
15
|
+
exists = true;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
exists = false;
|
|
19
|
+
}
|
|
20
|
+
if (!exists) {
|
|
21
|
+
throw new Error(`Artifact not found at: ${targetPath}`);
|
|
22
|
+
}
|
|
23
|
+
fs_1.default.rmSync(targetPath, { recursive: true, force: true });
|
|
24
|
+
}
|
|
25
|
+
function installArtifact(sourcePath, targetPath, method) {
|
|
26
|
+
if (!fs_1.default.existsSync(sourcePath)) {
|
|
27
|
+
throw new Error(`Source path does not exist: ${sourcePath}`);
|
|
28
|
+
}
|
|
29
|
+
const parentDir = path_1.default.dirname(targetPath);
|
|
30
|
+
if (!fs_1.default.existsSync(parentDir)) {
|
|
31
|
+
fs_1.default.mkdirSync(parentDir, { recursive: true });
|
|
32
|
+
}
|
|
33
|
+
// Clean up existing if it exists
|
|
34
|
+
fs_1.default.rmSync(targetPath, { recursive: true, force: true });
|
|
35
|
+
if (method === 'symlink') {
|
|
36
|
+
fs_1.default.symlinkSync(sourcePath, targetPath, 'dir');
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
fs_1.default.cpSync(sourcePath, targetPath, { recursive: true });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.detectExtensions = detectExtensions;
|
|
7
|
+
// src/core/init/detector.ts
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const FRONTEND_DEPS = ['next', 'react', 'vue', 'astro', 'svelte'];
|
|
11
|
+
const FRONTEND_DIRS = ['pages', 'app', 'landing'];
|
|
12
|
+
const DOCS_CONFIGS = ['mkdocs.yml', 'docusaurus.config.js', 'docusaurus.config.ts'];
|
|
13
|
+
const INFRA_MARKERS = ['Dockerfile', 'helm', 'terraform'];
|
|
14
|
+
function readPackageDeps(root) {
|
|
15
|
+
const pkgPath = path_1.default.join(root, 'package.json');
|
|
16
|
+
if (!fs_1.default.existsSync(pkgPath))
|
|
17
|
+
return { deps: {}, found: false };
|
|
18
|
+
try {
|
|
19
|
+
const pkg = JSON.parse(fs_1.default.readFileSync(pkgPath, 'utf-8'));
|
|
20
|
+
return { deps: { ...(pkg.dependencies ?? {}), ...(pkg.devDependencies ?? {}) }, found: true };
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return { deps: {}, found: true };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function docsHasContent(root) {
|
|
27
|
+
const docsDir = path_1.default.join(root, 'docs');
|
|
28
|
+
if (!fs_1.default.existsSync(docsDir) || !fs_1.default.statSync(docsDir).isDirectory())
|
|
29
|
+
return false;
|
|
30
|
+
const mdFiles = fs_1.default.readdirSync(docsDir).filter((f) => f.toLowerCase().endsWith('.md'));
|
|
31
|
+
// Un README suelto no cuenta; requiere config (manejada aparte) o ≥2 markdown.
|
|
32
|
+
return mdFiles.length >= 2;
|
|
33
|
+
}
|
|
34
|
+
function detectExtensions(root) {
|
|
35
|
+
const proposed = [];
|
|
36
|
+
const signals = [];
|
|
37
|
+
const deferred = [];
|
|
38
|
+
// frontend
|
|
39
|
+
const { deps } = readPackageDeps(root);
|
|
40
|
+
const frontDep = FRONTEND_DEPS.find((d) => d in deps);
|
|
41
|
+
const frontDir = FRONTEND_DIRS.find((d) => fs_1.default.existsSync(path_1.default.join(root, d)) && fs_1.default.statSync(path_1.default.join(root, d)).isDirectory());
|
|
42
|
+
if (frontDep || frontDir) {
|
|
43
|
+
proposed.push('frontend');
|
|
44
|
+
signals.push(frontDep ? `${frontDep} (package.json)` : `${frontDir}/`);
|
|
45
|
+
}
|
|
46
|
+
// docs
|
|
47
|
+
const docsConfig = DOCS_CONFIGS.find((c) => fs_1.default.existsSync(path_1.default.join(root, c)));
|
|
48
|
+
if (docsConfig) {
|
|
49
|
+
proposed.push('docs');
|
|
50
|
+
signals.push(`${docsConfig}`);
|
|
51
|
+
}
|
|
52
|
+
else if (docsHasContent(root)) {
|
|
53
|
+
proposed.push('docs');
|
|
54
|
+
signals.push('docs/ (≥2 .md)');
|
|
55
|
+
}
|
|
56
|
+
// infra (deferred — sin bundle aún)
|
|
57
|
+
let k8s = false;
|
|
58
|
+
try {
|
|
59
|
+
k8s = fs_1.default.readdirSync(root).some((f) => f.endsWith('.k8s.yaml'));
|
|
60
|
+
}
|
|
61
|
+
catch { /* directory vanished */ }
|
|
62
|
+
const infraMarker = INFRA_MARKERS.find((m) => fs_1.default.existsSync(path_1.default.join(root, m)));
|
|
63
|
+
if (k8s || infraMarker) {
|
|
64
|
+
deferred.push('infra (Fase futura)');
|
|
65
|
+
}
|
|
66
|
+
return { proposed, signals, deferred };
|
|
67
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runInitSteps = runInitSteps;
|
|
4
|
+
const steps_1 = require("./steps");
|
|
5
|
+
const checks_1 = require("../diagnostics/checks");
|
|
6
|
+
const context_1 = require("../diagnostics/context");
|
|
7
|
+
function wrapStep(id, level, fn) {
|
|
8
|
+
// id/level used only on error path; happy-path result carries its own id from the step function.
|
|
9
|
+
try {
|
|
10
|
+
const result = fn();
|
|
11
|
+
if (result instanceof Promise) {
|
|
12
|
+
return result.catch((e) => ({
|
|
13
|
+
id, level, action: 'failed',
|
|
14
|
+
error: e instanceof Error ? e.message : String(e),
|
|
15
|
+
}));
|
|
16
|
+
}
|
|
17
|
+
return Promise.resolve(result);
|
|
18
|
+
}
|
|
19
|
+
catch (e) {
|
|
20
|
+
return Promise.resolve({
|
|
21
|
+
id, level, action: 'failed',
|
|
22
|
+
error: e instanceof Error ? e.message : String(e),
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
async function runInitSteps(deps) {
|
|
27
|
+
const before = (0, checks_1.runChecks)(deps.ctx);
|
|
28
|
+
const steps = [];
|
|
29
|
+
// Nivel máquina (siempre)
|
|
30
|
+
steps.push(await wrapStep('machine.cache', 'machine', () => (0, steps_1.stepCache)(deps)));
|
|
31
|
+
steps.push(await wrapStep('machine.hook', 'machine', () => (0, steps_1.stepHook)(deps)));
|
|
32
|
+
steps.push(await wrapStep('machine.contextInjection', 'machine', () => (0, steps_1.stepContextInjection)(deps)));
|
|
33
|
+
steps.push(await wrapStep('machine.devCore', 'machine', () => (0, steps_1.stepDevCore)(deps)));
|
|
34
|
+
steps.push(await wrapStep('machine.globalSkills', 'machine', () => (0, steps_1.stepGlobalSkillsRepair)(deps)));
|
|
35
|
+
steps.push(await wrapStep('machine.ambient', 'machine', () => (0, steps_1.stepAmbient)(deps)));
|
|
36
|
+
// Nivel proyecto (solo en repo)
|
|
37
|
+
if (deps.ctx.project) {
|
|
38
|
+
steps.push(await wrapStep('project.profile', 'project', () => (0, steps_1.stepProfile)(deps)));
|
|
39
|
+
steps.push(await wrapStep('project.activation', 'project', () => (0, steps_1.stepActivation)(deps)));
|
|
40
|
+
steps.push(await wrapStep('project.sensors', 'project', () => (0, steps_1.stepSensors)(deps)));
|
|
41
|
+
steps.push(await wrapStep('project.constitution', 'project', () => (0, steps_1.stepConstitution)(deps)));
|
|
42
|
+
steps.push(await wrapStep('project.constitutionInjection', 'project', () => (0, steps_1.stepConstitutionInjection)(deps)));
|
|
43
|
+
steps.push(await wrapStep('project.context', 'project', () => (0, steps_1.stepContext)(deps)));
|
|
44
|
+
}
|
|
45
|
+
const after = (0, checks_1.runChecks)((0, context_1.gatherContext)({ cwd: deps.cwd, bundles: deps.bundles, agent: deps.agent }));
|
|
46
|
+
return {
|
|
47
|
+
steps,
|
|
48
|
+
applied: steps.filter((s) => s.action === 'applied').length,
|
|
49
|
+
pending: steps.filter((s) => s.action === 'pending').length,
|
|
50
|
+
failed: steps.filter((s) => s.action === 'failed').length,
|
|
51
|
+
before,
|
|
52
|
+
after,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/core/init/steps.ts
|
|
3
|
+
//
|
|
4
|
+
// Pure, ordered, idempotent steps over the diagnostics HarnessContext.
|
|
5
|
+
// Each step receives InitDeps (which carries the current snapshot + injected
|
|
6
|
+
// actions) and returns a StepResult describing what happened.
|
|
7
|
+
//
|
|
8
|
+
// defaultActions wires the real I/O implementations; tests inject spies.
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.defaultActions = void 0;
|
|
11
|
+
exports.stepCache = stepCache;
|
|
12
|
+
exports.stepHook = stepHook;
|
|
13
|
+
exports.stepDevCore = stepDevCore;
|
|
14
|
+
exports.stepGlobalSkillsRepair = stepGlobalSkillsRepair;
|
|
15
|
+
exports.stepAmbient = stepAmbient;
|
|
16
|
+
exports.stepProfile = stepProfile;
|
|
17
|
+
exports.stepActivation = stepActivation;
|
|
18
|
+
exports.stepSensors = stepSensors;
|
|
19
|
+
exports.stepConstitution = stepConstitution;
|
|
20
|
+
exports.stepConstitutionInjection = stepConstitutionInjection;
|
|
21
|
+
exports.stepContext = stepContext;
|
|
22
|
+
exports.stepContextInjection = stepContextInjection;
|
|
23
|
+
const registries_1 = require("../registries");
|
|
24
|
+
const install_1 = require("../../commands/hooks/install");
|
|
25
|
+
const bundle_install_1 = require("../bundle-install");
|
|
26
|
+
const init_1 = require("../../commands/sensors/init");
|
|
27
|
+
const profile_1 = require("../profile");
|
|
28
|
+
const context_1 = require("../diagnostics/context");
|
|
29
|
+
const detector_1 = require("./detector");
|
|
30
|
+
const orchestrator_1 = require("../context/orchestrator");
|
|
31
|
+
const providers_1 = require("../../providers");
|
|
32
|
+
const skill_integrity_1 = require("../skill-integrity");
|
|
33
|
+
const registries_2 = require("../registries");
|
|
34
|
+
const project_constitution_inject_1 = require("../context/project-constitution-inject");
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// defaultActions — bridges the real functions to the InitActions interface
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
const realInjectionOrchestrator = new orchestrator_1.InjectionOrchestrator();
|
|
39
|
+
exports.defaultActions = {
|
|
40
|
+
syncCache: async () => { await (0, registries_1.syncRegistries)(); },
|
|
41
|
+
installHook: (o) => (0, install_1.installHook)({
|
|
42
|
+
agent: o.agent,
|
|
43
|
+
registryRoot: o.registryRoot,
|
|
44
|
+
installMethod: o.installMethod,
|
|
45
|
+
}),
|
|
46
|
+
installBundle: (o) => (0, bundle_install_1.installBundle)({
|
|
47
|
+
bundleName: o.bundleName,
|
|
48
|
+
bundles: o.bundles,
|
|
49
|
+
agents: o.agents,
|
|
50
|
+
method: o.method,
|
|
51
|
+
projectRoot: o.projectRoot,
|
|
52
|
+
contentDir: o.contentDir,
|
|
53
|
+
}),
|
|
54
|
+
syncProfile: (o) => (0, bundle_install_1.syncProfile)({
|
|
55
|
+
projectRoot: o.projectRoot,
|
|
56
|
+
bundles: o.bundles,
|
|
57
|
+
agents: o.agents,
|
|
58
|
+
method: o.method,
|
|
59
|
+
contentDir: o.contentDir,
|
|
60
|
+
}),
|
|
61
|
+
initSensors: (o) => {
|
|
62
|
+
const result = (0, init_1.initSensors)({ cwd: o.cwd, registryRoot: o.registryRoot, configure: o.configure });
|
|
63
|
+
return { detection: result.detection };
|
|
64
|
+
},
|
|
65
|
+
addExtension: (root, name) => { (0, profile_1.addExtension)(root, name); },
|
|
66
|
+
ensureProfile: (root) => { (0, profile_1.ensureProfile)(root); },
|
|
67
|
+
gatherProject: (cwd, bundles, agent) => (0, context_1.gatherContext)({ cwd, bundles, agent }).project,
|
|
68
|
+
contextStatus: (op) => realInjectionOrchestrator.contextStatus(op),
|
|
69
|
+
installContext: (op) => { realInjectionOrchestrator.installContext(op); },
|
|
70
|
+
repairGlobalSkills: (skillsDir, registryContentDirs) => (0, skill_integrity_1.repairGlobalSkills)(skillsDir, registryContentDirs),
|
|
71
|
+
injectProjectConstitution: (o) => (0, project_constitution_inject_1.injectProjectConstitution)(o.projectRoot, o.agent),
|
|
72
|
+
};
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
// Step helpers
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
function ok(id, level, action, detail) {
|
|
77
|
+
return { id, level, action, detail };
|
|
78
|
+
}
|
|
79
|
+
function failed(id, level, error) {
|
|
80
|
+
return { id, level, action: 'failed', error };
|
|
81
|
+
}
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
// Machine-level steps
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
/** Step 1 – Sync the registry cache (clone / pull). */
|
|
86
|
+
async function stepCache(d) {
|
|
87
|
+
const { registryCache } = d.ctx.machine;
|
|
88
|
+
const needsSync = !registryCache.present || registryCache.gitState === 'behind';
|
|
89
|
+
if (!needsSync)
|
|
90
|
+
return ok('machine.cache', 'machine', 'skipped');
|
|
91
|
+
try {
|
|
92
|
+
await d.actions.syncCache();
|
|
93
|
+
return ok('machine.cache', 'machine', 'applied');
|
|
94
|
+
}
|
|
95
|
+
catch (e) {
|
|
96
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
97
|
+
return failed('machine.cache', 'machine', msg);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/** Step 2 – Install the session-start hook for the target agent. */
|
|
101
|
+
function stepHook(d) {
|
|
102
|
+
const { hook } = d.ctx.machine;
|
|
103
|
+
if (hook.present && !hook.degraded)
|
|
104
|
+
return ok('machine.hook', 'machine', 'skipped');
|
|
105
|
+
d.actions.installHook({
|
|
106
|
+
agent: d.agent,
|
|
107
|
+
registryRoot: d.registryRoot,
|
|
108
|
+
installMethod: d.installMethod,
|
|
109
|
+
});
|
|
110
|
+
return ok('machine.hook', 'machine', 'applied');
|
|
111
|
+
}
|
|
112
|
+
/** Step 3 – Install / repair the baseline bundle (dev-core). */
|
|
113
|
+
function stepDevCore(d) {
|
|
114
|
+
const { devCore } = d.ctx.machine;
|
|
115
|
+
if (devCore.present && devCore.brokenLinks.length === 0) {
|
|
116
|
+
return ok('machine.devCore', 'machine', 'skipped');
|
|
117
|
+
}
|
|
118
|
+
// Find the baseline bundle (there may be several; pick first baseline)
|
|
119
|
+
const baselineBundle = d.bundles.find((b) => b.scope === 'baseline');
|
|
120
|
+
const bundleName = baselineBundle?.name ?? 'dev';
|
|
121
|
+
d.actions.installBundle({
|
|
122
|
+
bundleName,
|
|
123
|
+
bundles: d.bundles,
|
|
124
|
+
agents: [d.agent],
|
|
125
|
+
method: d.installMethod,
|
|
126
|
+
projectRoot: d.cwd,
|
|
127
|
+
contentDir: d.contentDir,
|
|
128
|
+
});
|
|
129
|
+
return ok('machine.devCore', 'machine', 'applied');
|
|
130
|
+
}
|
|
131
|
+
/** Step 3.5 – Repair broken global skill symlinks (orphans outside the baseline). */
|
|
132
|
+
function stepGlobalSkillsRepair(d) {
|
|
133
|
+
const { globalSkills } = d.ctx.machine;
|
|
134
|
+
const broken = globalSkills.repairable.length + globalSkills.dead.length;
|
|
135
|
+
if (broken === 0)
|
|
136
|
+
return ok('machine.globalSkills', 'machine', 'skipped');
|
|
137
|
+
const skillsDir = providers_1.PROVIDERS[d.agent].skill.global;
|
|
138
|
+
const r = d.actions.repairGlobalSkills(skillsDir, (0, registries_2.contentRoots)());
|
|
139
|
+
return ok('machine.globalSkills', 'machine', 'applied', `re-linked ${r.relinked.length}, pruned ${r.pruned.length}`);
|
|
140
|
+
}
|
|
141
|
+
/** Step 4 – Install missing ambient bundles. */
|
|
142
|
+
function stepAmbient(d) {
|
|
143
|
+
const { wanted, installed } = d.ctx.machine.ambient;
|
|
144
|
+
if (wanted.length === 0)
|
|
145
|
+
return ok('machine.ambient', 'machine', 'skipped');
|
|
146
|
+
const missing = wanted.filter((w) => !installed.includes(w));
|
|
147
|
+
if (missing.length === 0)
|
|
148
|
+
return ok('machine.ambient', 'machine', 'skipped');
|
|
149
|
+
for (const bundleName of missing) {
|
|
150
|
+
d.actions.installBundle({
|
|
151
|
+
bundleName,
|
|
152
|
+
bundles: d.bundles,
|
|
153
|
+
agents: [d.agent],
|
|
154
|
+
method: d.installMethod,
|
|
155
|
+
projectRoot: d.cwd,
|
|
156
|
+
contentDir: d.contentDir,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
return ok('machine.ambient', 'machine', 'applied', `installed: ${missing.join(', ')}`);
|
|
160
|
+
}
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
// Project-level steps
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
/** Step 5 – Detect and confirm project extensions, then add them to the profile. */
|
|
165
|
+
async function stepProfile(d) {
|
|
166
|
+
const proj = d.ctx.project;
|
|
167
|
+
if (!proj)
|
|
168
|
+
return ok('project.profile', 'project', 'skipped', 'no project');
|
|
169
|
+
const { proposed, signals } = (0, detector_1.detectExtensions)(proj.root);
|
|
170
|
+
const alreadyPresent = proj.profile.extensions;
|
|
171
|
+
const newProposed = proposed.filter((p) => !alreadyPresent.includes(p));
|
|
172
|
+
if (newProposed.length === 0)
|
|
173
|
+
return bootstrapOrSkip(d, proj, 'sin extensiones nuevas');
|
|
174
|
+
const confirmed = await d.confirmExtensions(newProposed, signals);
|
|
175
|
+
if (confirmed.length === 0)
|
|
176
|
+
return bootstrapOrSkip(d, proj, 'sin extensiones confirmadas');
|
|
177
|
+
for (const name of confirmed) {
|
|
178
|
+
d.actions.addExtension(proj.root, name);
|
|
179
|
+
}
|
|
180
|
+
return ok('project.profile', 'project', 'applied', `added: ${confirmed.join(', ')}`);
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* No new extensions to add. If the profile file doesn't exist yet, bootstrap an
|
|
184
|
+
* empty one so the project is marked initialized — otherwise `awm init` would
|
|
185
|
+
* leave `.awm/profile.json` missing and the diagnostic would self-referentially
|
|
186
|
+
* suggest re-running `awm init`. If it already exists, this is a true no-op.
|
|
187
|
+
*/
|
|
188
|
+
function bootstrapOrSkip(d, proj, skipDetail) {
|
|
189
|
+
if (!proj.profile.present) {
|
|
190
|
+
d.actions.ensureProfile(proj.root);
|
|
191
|
+
return ok('project.profile', 'project', 'applied', 'perfil inicializado (sin extensiones)');
|
|
192
|
+
}
|
|
193
|
+
return ok('project.profile', 'project', 'skipped', skipDetail);
|
|
194
|
+
}
|
|
195
|
+
/** Step 6 – Re-gather project facts and sync the profile symlinks if needed. */
|
|
196
|
+
function stepActivation(d) {
|
|
197
|
+
const proj = d.ctx.project;
|
|
198
|
+
if (!proj)
|
|
199
|
+
return ok('project.activation', 'project', 'skipped', 'no project');
|
|
200
|
+
// Re-read project state so we pick up extensions added by stepProfile
|
|
201
|
+
const fresh = d.actions.gatherProject(proj.root, d.bundles, d.agent) ?? proj;
|
|
202
|
+
const { expected, linked, broken } = fresh.activeBundles;
|
|
203
|
+
const allLinked = expected.every((e) => linked.includes(e)) && broken.length === 0;
|
|
204
|
+
if (allLinked)
|
|
205
|
+
return ok('project.activation', 'project', 'skipped');
|
|
206
|
+
d.actions.syncProfile({
|
|
207
|
+
projectRoot: proj.root,
|
|
208
|
+
bundles: d.bundles,
|
|
209
|
+
agents: [d.agent],
|
|
210
|
+
method: d.installMethod,
|
|
211
|
+
contentDir: d.contentDir,
|
|
212
|
+
});
|
|
213
|
+
return ok('project.activation', 'project', 'applied');
|
|
214
|
+
}
|
|
215
|
+
/** Step 7 – Initialize the sensor manifest if absent. */
|
|
216
|
+
function stepSensors(d) {
|
|
217
|
+
const proj = d.ctx.project;
|
|
218
|
+
if (!proj)
|
|
219
|
+
return ok('project.sensors', 'project', 'skipped', 'no project');
|
|
220
|
+
if (proj.sensors.present)
|
|
221
|
+
return ok('project.sensors', 'project', 'skipped');
|
|
222
|
+
d.actions.initSensors({ cwd: proj.root, registryRoot: d.sensorPacksRoot, configure: true });
|
|
223
|
+
return ok('project.sensors', 'project', 'applied');
|
|
224
|
+
}
|
|
225
|
+
/** Step 8 – Signal that the agent should run the project-constitution skill. */
|
|
226
|
+
function stepConstitution(d) {
|
|
227
|
+
const proj = d.ctx.project;
|
|
228
|
+
if (!proj)
|
|
229
|
+
return ok('project.constitution', 'project', 'skipped', 'no project');
|
|
230
|
+
if (proj.constitution.present)
|
|
231
|
+
return ok('project.constitution', 'project', 'skipped');
|
|
232
|
+
return ok('project.constitution', 'project', 'pending', 'skill: project-constitution');
|
|
233
|
+
}
|
|
234
|
+
/** Step 8b – Entregar CONSTITUTION.md a agentes con inyección config-instructions
|
|
235
|
+
* (opencode) vía un opencode.json local del proyecto. Claude lo recibe por el hook. */
|
|
236
|
+
function stepConstitutionInjection(d) {
|
|
237
|
+
const proj = d.ctx.project;
|
|
238
|
+
if (!proj)
|
|
239
|
+
return ok('project.constitutionInjection', 'project', 'skipped', 'no project');
|
|
240
|
+
const inj = (0, providers_1.getInjection)(d.agent);
|
|
241
|
+
if (!inj || inj.type !== 'config-instructions') {
|
|
242
|
+
return ok('project.constitutionInjection', 'project', 'skipped', 'cubierto por hook');
|
|
243
|
+
}
|
|
244
|
+
if (!proj.constitution.present) {
|
|
245
|
+
return ok('project.constitutionInjection', 'project', 'skipped', 'sin CONSTITUTION.md');
|
|
246
|
+
}
|
|
247
|
+
const res = d.actions.injectProjectConstitution({ projectRoot: proj.root, agent: d.agent });
|
|
248
|
+
if (res === 'injected')
|
|
249
|
+
return ok('project.constitutionInjection', 'project', 'applied');
|
|
250
|
+
return ok('project.constitutionInjection', 'project', 'skipped', res);
|
|
251
|
+
}
|
|
252
|
+
/** Step 9 – Signal that the agent should run the project-context-init skill. */
|
|
253
|
+
function stepContext(d) {
|
|
254
|
+
const proj = d.ctx.project;
|
|
255
|
+
if (!proj)
|
|
256
|
+
return ok('project.context', 'project', 'skipped', 'no project');
|
|
257
|
+
if (proj.context.present)
|
|
258
|
+
return ok('project.context', 'project', 'skipped');
|
|
259
|
+
return ok('project.context', 'project', 'pending', 'skill: project-context-init');
|
|
260
|
+
}
|
|
261
|
+
/** Step 2b – Inject AWM context for agents whose mechanism isn't the Claude hook. */
|
|
262
|
+
function stepContextInjection(d) {
|
|
263
|
+
const inj = (0, providers_1.getInjection)(d.agent);
|
|
264
|
+
if (!inj)
|
|
265
|
+
return ok('machine.contextInjection', 'machine', 'skipped', 'sin mecanismo de inyección');
|
|
266
|
+
if (inj.type === 'cc-settings-merge')
|
|
267
|
+
return ok('machine.contextInjection', 'machine', 'skipped', 'cubierto por hook');
|
|
268
|
+
const op = {
|
|
269
|
+
agent: d.agent,
|
|
270
|
+
scope: 'global',
|
|
271
|
+
registryRoot: d.registryRoot,
|
|
272
|
+
installMethod: d.installMethod,
|
|
273
|
+
profileExtensions: [],
|
|
274
|
+
};
|
|
275
|
+
if (d.actions.contextStatus(op) === 'injected')
|
|
276
|
+
return ok('machine.contextInjection', 'machine', 'skipped');
|
|
277
|
+
d.actions.installContext(op);
|
|
278
|
+
return ok('machine.contextInjection', 'machine', 'applied');
|
|
279
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.detectBranch = detectBranch;
|
|
7
|
+
exports.ledgerPath = ledgerPath;
|
|
8
|
+
exports.addEntry = addEntry;
|
|
9
|
+
exports.listEntries = listEntries;
|
|
10
|
+
exports.recurring = recurring;
|
|
11
|
+
exports.archiveLedger = archiveLedger;
|
|
12
|
+
const fs_1 = __importDefault(require("fs"));
|
|
13
|
+
const path_1 = __importDefault(require("path"));
|
|
14
|
+
const child_process_1 = require("child_process");
|
|
15
|
+
const LEDGER_DIR = path_1.default.join('.awm', 'ledger');
|
|
16
|
+
function detectBranch(cwd) {
|
|
17
|
+
try {
|
|
18
|
+
const b = (0, child_process_1.execSync)('git rev-parse --abbrev-ref HEAD', {
|
|
19
|
+
cwd, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'], timeout: 3000,
|
|
20
|
+
}).trim();
|
|
21
|
+
return b && b !== 'HEAD' ? b : '_no-branch';
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return '_no-branch';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function ledgerPath(cwd, branch) {
|
|
28
|
+
const safe = branch.replace(/\//g, '__');
|
|
29
|
+
return path_1.default.join(cwd, LEDGER_DIR, `${safe}.jsonl`);
|
|
30
|
+
}
|
|
31
|
+
function addEntry(cwd, entry) {
|
|
32
|
+
const p = ledgerPath(cwd, entry.branch);
|
|
33
|
+
fs_1.default.mkdirSync(path_1.default.dirname(p), { recursive: true });
|
|
34
|
+
fs_1.default.appendFileSync(p, JSON.stringify(entry) + '\n', 'utf-8');
|
|
35
|
+
}
|
|
36
|
+
function listEntries(cwd, branch) {
|
|
37
|
+
const p = ledgerPath(cwd, branch);
|
|
38
|
+
if (!fs_1.default.existsSync(p))
|
|
39
|
+
return [];
|
|
40
|
+
const out = [];
|
|
41
|
+
for (const line of fs_1.default.readFileSync(p, 'utf-8').split('\n')) {
|
|
42
|
+
const trimmed = line.trim();
|
|
43
|
+
if (!trimmed)
|
|
44
|
+
continue;
|
|
45
|
+
try {
|
|
46
|
+
out.push(JSON.parse(trimmed));
|
|
47
|
+
}
|
|
48
|
+
catch { /* skip malformed line */ }
|
|
49
|
+
}
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
function recurring(cwd, branch, min) {
|
|
53
|
+
const bySig = new Map();
|
|
54
|
+
for (const e of listEntries(cwd, branch)) {
|
|
55
|
+
const arr = bySig.get(e.signature) ?? [];
|
|
56
|
+
arr.push(e);
|
|
57
|
+
bySig.set(e.signature, arr);
|
|
58
|
+
}
|
|
59
|
+
return [...bySig.entries()]
|
|
60
|
+
.map(([signature, entries]) => ({ signature, count: entries.length, entries }))
|
|
61
|
+
.filter(c => c.count >= min)
|
|
62
|
+
.sort((a, b) => b.count - a.count);
|
|
63
|
+
}
|
|
64
|
+
function archiveLedger(cwd, branch, label) {
|
|
65
|
+
const src = ledgerPath(cwd, branch);
|
|
66
|
+
if (!fs_1.default.existsSync(src))
|
|
67
|
+
return false;
|
|
68
|
+
const safe = branch.replace(/\//g, '__');
|
|
69
|
+
const dst = path_1.default.join(cwd, LEDGER_DIR, 'archive', `${safe}-${label}.jsonl`);
|
|
70
|
+
fs_1.default.mkdirSync(path_1.default.dirname(dst), { recursive: true });
|
|
71
|
+
fs_1.default.renameSync(src, dst);
|
|
72
|
+
return true;
|
|
73
|
+
}
|