agentic-workflow-manager 2.0.0 → 2.1.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/dist/src/commands/doctor.js +9 -7
- package/dist/src/commands/hooks/install.js +10 -4
- package/dist/src/commands/hooks/uninstall.js +2 -2
- package/dist/src/commands/init.js +12 -10
- package/dist/src/commands/registry/index.js +2 -2
- package/dist/src/commands/sensors/formatters/eslint.js +7 -1
- package/dist/src/commands/sensors/index.js +2 -2
- package/dist/src/commands/sensors/install.js +3 -4
- package/dist/src/commands/sensors/status.js +3 -3
- package/dist/src/core/context/materializer.js +2 -5
- package/dist/src/core/context/provider.js +2 -2
- package/dist/src/core/diagnostics/checks.js +18 -18
- package/dist/src/core/diagnostics/context.js +2 -4
- package/dist/src/core/init/steps.js +7 -7
- package/dist/src/core/paths.js +56 -0
- package/dist/src/core/registries.js +3 -3
- package/dist/src/core/update-check.js +7 -8
- package/dist/src/index.js +8 -6
- package/dist/src/providers/index.js +4 -4
- package/dist/src/utils/config.js +2 -2
- package/dist/tests/commands/doctor-platform.test.js +22 -0
- package/dist/tests/commands/doctor.test.js +8 -8
- package/dist/tests/commands/hooks/install-symlink-fallback.test.js +59 -0
- package/dist/tests/commands/init.test.js +5 -5
- package/dist/tests/commands/sensors/eslint-formatter.test.js +34 -0
- package/dist/tests/commands/sensors/status.test.js +2 -2
- package/dist/tests/core/diagnostics/checks.test.js +1 -1
- package/dist/tests/core/paths.test.js +78 -0
- package/package.json +4 -2
|
@@ -9,6 +9,7 @@ exports.registerDoctorCommand = registerDoctorCommand;
|
|
|
9
9
|
const picocolors_1 = __importDefault(require("picocolors"));
|
|
10
10
|
const context_1 = require("../core/diagnostics/context");
|
|
11
11
|
const checks_1 = require("../core/diagnostics/checks");
|
|
12
|
+
const paths_1 = require("../core/paths");
|
|
12
13
|
function glyph(status) {
|
|
13
14
|
if (status === 'ok')
|
|
14
15
|
return picocolors_1.default.green('✔');
|
|
@@ -30,24 +31,25 @@ function line(r) {
|
|
|
30
31
|
}
|
|
31
32
|
function renderReport(report) {
|
|
32
33
|
const lines = [];
|
|
33
|
-
lines.push(picocolors_1.default.bold('AWM ·
|
|
34
|
+
lines.push(picocolors_1.default.bold('AWM · harness status'));
|
|
34
35
|
lines.push('');
|
|
35
|
-
lines.push('
|
|
36
|
+
lines.push('Machine (global)');
|
|
37
|
+
lines.push(picocolors_1.default.dim(` platform: ${(0, paths_1.platformLabel)()}`));
|
|
36
38
|
for (const r of report.results.filter((x) => x.level === 'machine'))
|
|
37
39
|
lines.push(line(r));
|
|
38
40
|
lines.push('');
|
|
39
41
|
if (report.hasProject) {
|
|
40
|
-
lines.push(`
|
|
42
|
+
lines.push(`Project: ${report.projectName ?? ''}`.trimEnd());
|
|
41
43
|
for (const r of report.results.filter((x) => x.level === 'project'))
|
|
42
44
|
lines.push(line(r));
|
|
43
45
|
}
|
|
44
46
|
else {
|
|
45
|
-
lines.push(picocolors_1.default.dim('(
|
|
47
|
+
lines.push(picocolors_1.default.dim('(no project in cwd)'));
|
|
46
48
|
}
|
|
47
49
|
lines.push('');
|
|
48
50
|
const actions = report.results.filter((r) => r.remedy.kind !== 'none').length;
|
|
49
|
-
const
|
|
50
|
-
lines.push(`
|
|
51
|
+
const status = report.overall === 'healthy' ? picocolors_1.default.green('healthy') : picocolors_1.default.red('degraded');
|
|
52
|
+
lines.push(`status: ${status} · ${actions} suggested actions`);
|
|
51
53
|
return lines.join('\n');
|
|
52
54
|
}
|
|
53
55
|
function runDoctor(opts = {}) {
|
|
@@ -56,7 +58,7 @@ function runDoctor(opts = {}) {
|
|
|
56
58
|
report = (0, checks_1.runChecks)((0, context_1.gatherContext)({ cwd: opts.cwd }));
|
|
57
59
|
}
|
|
58
60
|
catch (err) {
|
|
59
|
-
process.stderr.write(`awm doctor: error
|
|
61
|
+
process.stderr.write(`awm doctor: internal error: ${err.message}\n`);
|
|
60
62
|
return 2;
|
|
61
63
|
}
|
|
62
64
|
if (opts.json) {
|
|
@@ -8,6 +8,7 @@ exports.installHook = installHook;
|
|
|
8
8
|
const fs_1 = __importDefault(require("fs"));
|
|
9
9
|
const path_1 = __importDefault(require("path"));
|
|
10
10
|
const providers_1 = require("../../providers");
|
|
11
|
+
const paths_1 = require("../../core/paths");
|
|
11
12
|
function syncFile(source, dest, method) {
|
|
12
13
|
try {
|
|
13
14
|
fs_1.default.unlinkSync(dest);
|
|
@@ -26,8 +27,7 @@ function syncFile(source, dest, method) {
|
|
|
26
27
|
function backupSettings(settingsPath) {
|
|
27
28
|
if (!fs_1.default.existsSync(settingsPath))
|
|
28
29
|
return null;
|
|
29
|
-
const
|
|
30
|
-
const backupDir = path_1.default.join(awmHome, 'backups');
|
|
30
|
+
const backupDir = path_1.default.join((0, paths_1.awmHome)(), 'backups');
|
|
31
31
|
fs_1.default.mkdirSync(backupDir, { recursive: true });
|
|
32
32
|
const ts = new Date().toISOString().replace(/[:.]/g, '-').replace('T', '-').slice(0, 19);
|
|
33
33
|
const backupPath = path_1.default.join(backupDir, `settings.json.${ts}.bak`);
|
|
@@ -57,13 +57,19 @@ function installHook(options) {
|
|
|
57
57
|
fs_1.default.mkdirSync(config.scriptsDir, { recursive: true });
|
|
58
58
|
syncFile(path_1.default.join(sourceHooks, 'session-start'), path_1.default.join(config.scriptsDir, 'session-start'), options.installMethod);
|
|
59
59
|
syncFile(path_1.default.join(sourceHooks, 'run-hook.cmd'), path_1.default.join(config.scriptsDir, 'run-hook.cmd'), options.installMethod);
|
|
60
|
-
// 3.
|
|
60
|
+
// 3. Link the skill (default: symlink so 'awm update' propagates; fall back to copy if symlink is unavailable, e.g. Windows without Developer Mode)
|
|
61
61
|
const skillDest = path_1.default.join(config.scriptsDir, 'using-awm.md');
|
|
62
62
|
try {
|
|
63
63
|
fs_1.default.unlinkSync(skillDest);
|
|
64
64
|
}
|
|
65
65
|
catch { /* not exists */ }
|
|
66
|
-
|
|
66
|
+
try {
|
|
67
|
+
fs_1.default.symlinkSync(sourceSkill, skillDest);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// best-effort: copy the single skill file; 'awm update' will not auto-propagate
|
|
71
|
+
fs_1.default.copyFileSync(sourceSkill, skillDest);
|
|
72
|
+
}
|
|
67
73
|
// 4. Backup settings if it exists
|
|
68
74
|
const backupPath = backupSettings(config.settingsPath);
|
|
69
75
|
// 5. Read or initialize settings
|
|
@@ -7,11 +7,11 @@ exports.uninstallHook = uninstallHook;
|
|
|
7
7
|
const fs_1 = __importDefault(require("fs"));
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
const providers_1 = require("../../providers");
|
|
10
|
+
const paths_1 = require("../../core/paths");
|
|
10
11
|
function backupSettings(settingsPath) {
|
|
11
12
|
if (!fs_1.default.existsSync(settingsPath))
|
|
12
13
|
return null;
|
|
13
|
-
const
|
|
14
|
-
const backupDir = path_1.default.join(awmHome, 'backups');
|
|
14
|
+
const backupDir = path_1.default.join((0, paths_1.awmHome)(), 'backups');
|
|
15
15
|
fs_1.default.mkdirSync(backupDir, { recursive: true });
|
|
16
16
|
const ts = new Date().toISOString().replace(/[:.]/g, '-').replace('T', '-').slice(0, 19);
|
|
17
17
|
const backupPath = path_1.default.join(backupDir, `settings.json.${ts}.bak`);
|
|
@@ -49,6 +49,7 @@ const bundles_1 = require("../core/bundles");
|
|
|
49
49
|
const registries_1 = require("../core/registries");
|
|
50
50
|
const orchestrator_1 = require("../core/init/orchestrator");
|
|
51
51
|
const steps_1 = require("../core/init/steps");
|
|
52
|
+
const paths_1 = require("../core/paths");
|
|
52
53
|
// ---------------------------------------------------------------------------
|
|
53
54
|
// Rendering
|
|
54
55
|
// ---------------------------------------------------------------------------
|
|
@@ -65,26 +66,26 @@ function renderInitOutcome(o) {
|
|
|
65
66
|
const lines = [];
|
|
66
67
|
lines.push(picocolors_1.default.bold('AWM · init'));
|
|
67
68
|
lines.push('');
|
|
68
|
-
// ---
|
|
69
|
-
lines.push(picocolors_1.default.bold('
|
|
69
|
+
// --- Initial state ---
|
|
70
|
+
lines.push(picocolors_1.default.bold('Initial state'));
|
|
70
71
|
lines.push((0, doctor_1.renderReport)(o.before));
|
|
71
72
|
lines.push('');
|
|
72
|
-
// ---
|
|
73
|
-
lines.push(picocolors_1.default.bold('
|
|
73
|
+
// --- Actions ---
|
|
74
|
+
lines.push(picocolors_1.default.bold('Actions'));
|
|
74
75
|
for (const s of o.steps) {
|
|
75
76
|
const det = s.detail ? picocolors_1.default.dim(` ${s.detail}`) : '';
|
|
76
77
|
const err = s.error ? picocolors_1.default.red(` [${s.error}]`) : '';
|
|
77
78
|
lines.push(` ${stepGlyph(s.action)} ${s.id}${det}${err}`);
|
|
78
79
|
}
|
|
79
80
|
lines.push('');
|
|
80
|
-
// ---
|
|
81
|
-
lines.push(picocolors_1.default.bold('
|
|
81
|
+
// --- Final state ---
|
|
82
|
+
lines.push(picocolors_1.default.bold('Final state'));
|
|
82
83
|
lines.push((0, doctor_1.renderReport)(o.after));
|
|
83
84
|
lines.push('');
|
|
84
85
|
// --- Summary ---
|
|
85
86
|
const pendingCount = o.pending;
|
|
86
|
-
const
|
|
87
|
-
lines.push(`
|
|
87
|
+
const status = o.after.overall === 'healthy' ? picocolors_1.default.green('healthy') : picocolors_1.default.red('degraded');
|
|
88
|
+
lines.push(`status: ${status} · ${pendingCount} steps require an agent (skills above)`);
|
|
88
89
|
return lines.join('\n');
|
|
89
90
|
}
|
|
90
91
|
async function runInit(opts = {}) {
|
|
@@ -121,7 +122,7 @@ async function runInit(opts = {}) {
|
|
|
121
122
|
});
|
|
122
123
|
}
|
|
123
124
|
catch (err) {
|
|
124
|
-
process.stderr.write(`awm init: error
|
|
125
|
+
process.stderr.write(`awm init: internal error: ${err.message}\n`);
|
|
125
126
|
return 2;
|
|
126
127
|
}
|
|
127
128
|
if (opts.json) {
|
|
@@ -149,7 +150,7 @@ function makeConfirmExtensions(yes) {
|
|
|
149
150
|
return [];
|
|
150
151
|
const { multiselect, isCancel } = await Promise.resolve().then(() => __importStar(require('@clack/prompts')));
|
|
151
152
|
const choice = await multiselect({
|
|
152
|
-
message: `
|
|
153
|
+
message: `Extensions detected (${signals.join(', ')}) — activate?`,
|
|
153
154
|
options: proposed.map((p) => ({ value: p, label: p })),
|
|
154
155
|
initialValues: proposed,
|
|
155
156
|
required: false,
|
|
@@ -170,6 +171,7 @@ function registerInitCommand(program) {
|
|
|
170
171
|
.option('--machine-only', 'Only run machine-level steps (skip project steps)')
|
|
171
172
|
.option('--json', 'Emit the InitOutcome as JSON')
|
|
172
173
|
.action(async (options) => {
|
|
174
|
+
(0, paths_1.warnIfUnsupportedPlatform)((m) => console.warn(picocolors_1.default.yellow(`⚠ ${m}`)));
|
|
173
175
|
const code = await runInit({
|
|
174
176
|
yes: options.yes,
|
|
175
177
|
agent: options.agent,
|
|
@@ -124,8 +124,8 @@ function registerRegistryCommand(program) {
|
|
|
124
124
|
console.log(`${picocolors_1.default.cyan(r.name)} ${r.remote} ${picocolors_1.default.dim(counts)}`);
|
|
125
125
|
for (const o of (0, status_1.overrideStatus)(r.contentRoot, earlier)) {
|
|
126
126
|
console.log(o.active
|
|
127
|
-
? picocolors_1.default.yellow(` ↑ override
|
|
128
|
-
: picocolors_1.default.dim(` ∅ override
|
|
127
|
+
? picocolors_1.default.yellow(` ↑ override active: ${o.name}`)
|
|
128
|
+
: picocolors_1.default.dim(` ∅ override with no effect: ${o.name}`));
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
131
|
catch (e) {
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.parseEslintOutput = parseEslintOutput;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
4
8
|
function parseEslintOutput(raw) {
|
|
5
9
|
let parsed;
|
|
6
10
|
try {
|
|
@@ -15,7 +19,9 @@ function parseEslintOutput(raw) {
|
|
|
15
19
|
for (const msg of file.messages) {
|
|
16
20
|
if (msg.severity < 2 || msg.line == null || msg.column == null)
|
|
17
21
|
continue;
|
|
18
|
-
const rel = file.filePath.startsWith(cwd +
|
|
22
|
+
const rel = file.filePath.startsWith(cwd + path_1.default.sep)
|
|
23
|
+
? path_1.default.relative(cwd, file.filePath)
|
|
24
|
+
: file.filePath;
|
|
19
25
|
errors.push({
|
|
20
26
|
file: rel,
|
|
21
27
|
line: msg.line,
|
|
@@ -58,8 +58,8 @@ function registerSensorsCommand(program) {
|
|
|
58
58
|
const writeDir = manifestDir ?? process.cwd();
|
|
59
59
|
(0, baseline_1.writeBaseline)(writeDir, baseline);
|
|
60
60
|
const total = Object.values(baseline).reduce((n, fps) => n + fps.length, 0);
|
|
61
|
-
prompts_1.log.success(`Baseline
|
|
62
|
-
prompts_1.log.info('
|
|
61
|
+
prompts_1.log.success(`Baseline saved: ${total} findings accepted in .awm/sensors.baseline.json`);
|
|
62
|
+
prompts_1.log.info('Sensors now fail only on new findings. Re-run `awm sensors baseline` after reducing debt.');
|
|
63
63
|
});
|
|
64
64
|
sensors
|
|
65
65
|
.command('status')
|
|
@@ -7,12 +7,12 @@ exports.installSensorHook = installSensorHook;
|
|
|
7
7
|
exports.uninstallSensorHook = uninstallSensorHook;
|
|
8
8
|
const fs_1 = __importDefault(require("fs"));
|
|
9
9
|
const path_1 = __importDefault(require("path"));
|
|
10
|
-
const
|
|
10
|
+
const paths_1 = require("../../core/paths");
|
|
11
11
|
const POST_TOOL_USE_EVENT = 'PostToolUse';
|
|
12
12
|
const POST_TOOL_USE_MATCHER = 'Write|Edit|MultiEdit';
|
|
13
13
|
const AWM_SENSOR_CMD = 'awm sensors run --fast';
|
|
14
14
|
function defaultSettingsPath() {
|
|
15
|
-
return path_1.default.join(
|
|
15
|
+
return path_1.default.join((0, paths_1.homeDir)(), '.claude', 'settings.json');
|
|
16
16
|
}
|
|
17
17
|
function readSettings(p) {
|
|
18
18
|
if (!fs_1.default.existsSync(p))
|
|
@@ -31,8 +31,7 @@ function isAwmEntry(e) {
|
|
|
31
31
|
function backupSettings(settingsPath) {
|
|
32
32
|
if (!fs_1.default.existsSync(settingsPath))
|
|
33
33
|
return undefined;
|
|
34
|
-
const
|
|
35
|
-
const backupDir = path_1.default.join(awmHome, 'backups');
|
|
34
|
+
const backupDir = path_1.default.join((0, paths_1.awmHome)(), 'backups');
|
|
36
35
|
fs_1.default.mkdirSync(backupDir, { recursive: true });
|
|
37
36
|
const ts = new Date().toISOString().replace(/[:.]/g, '-').replace('T', '-').slice(0, 19);
|
|
38
37
|
const backupPath = path_1.default.join(backupDir, `settings.json.${ts}.sensor.bak`);
|
|
@@ -20,7 +20,7 @@ function configCheck(parts, cwd) {
|
|
|
20
20
|
const i = parts.indexOf('--config');
|
|
21
21
|
const cfg = i !== -1 ? parts[i + 1] : undefined;
|
|
22
22
|
if (cfg && !fs_1.default.existsSync(path_1.default.join(cwd, cfg))) {
|
|
23
|
-
return { ok: false, detail: `config
|
|
23
|
+
return { ok: false, detail: `missing config: ${cfg}` };
|
|
24
24
|
}
|
|
25
25
|
return null;
|
|
26
26
|
}
|
|
@@ -38,12 +38,12 @@ function checkCmd(cmd, cwd) {
|
|
|
38
38
|
if (bin === 'npx') {
|
|
39
39
|
const tool = npxTool(parts);
|
|
40
40
|
if (!tool)
|
|
41
|
-
return { ok: false, detail: 'npx
|
|
41
|
+
return { ok: false, detail: 'npx without a tool specified' };
|
|
42
42
|
const localBin = path_1.default.join(cwd, 'node_modules', '.bin', tool);
|
|
43
43
|
if (!fs_1.default.existsSync(localBin)) {
|
|
44
44
|
return {
|
|
45
45
|
ok: false,
|
|
46
|
-
detail: `${tool}
|
|
46
|
+
detail: `${tool} not installed locally (npx would download a remote package) — add it to devDependencies`,
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
49
|
return configCheck(parts, cwd) ?? { ok: true, detail: `${tool} (node_modules/.bin)` };
|
|
@@ -7,14 +7,11 @@ exports.globalContextPath = globalContextPath;
|
|
|
7
7
|
exports.materialize = materialize;
|
|
8
8
|
// cli/src/core/context/materializer.ts
|
|
9
9
|
const fs_1 = __importDefault(require("fs"));
|
|
10
|
-
const os_1 = __importDefault(require("os"));
|
|
11
10
|
const path_1 = __importDefault(require("path"));
|
|
12
11
|
const provider_1 = require("./provider");
|
|
13
|
-
|
|
14
|
-
return process.env.AWM_HOME || path_1.default.join(process.env.HOME || os_1.default.homedir(), '.awm');
|
|
15
|
-
}
|
|
12
|
+
const paths_1 = require("../paths");
|
|
16
13
|
function globalContextPath() {
|
|
17
|
-
return path_1.default.join(awmHome(), 'context', 'awm-context.md');
|
|
14
|
+
return path_1.default.join((0, paths_1.awmHome)(), 'context', 'awm-context.md');
|
|
18
15
|
}
|
|
19
16
|
function materialize(ctx, absPath, scope) {
|
|
20
17
|
let onDisk = null;
|
|
@@ -22,8 +22,8 @@ function buildContext(input) {
|
|
|
22
22
|
throw new Error(`using-awm skill not found at ${skillPath}. Run 'awm update' first.`);
|
|
23
23
|
}
|
|
24
24
|
const skill = fs_1.default.readFileSync(skillPath, 'utf-8');
|
|
25
|
-
const exts = input.profileExtensions.length ? input.profileExtensions.join(', ') : '
|
|
26
|
-
const header = `<!-- AWM context (generated) -->\n# AWM\n\
|
|
25
|
+
const exts = input.profileExtensions.length ? input.profileExtensions.join(', ') : 'none';
|
|
26
|
+
const header = `<!-- AWM context (generated) -->\n# AWM\n\nActive extensions: ${exts}\n\n`;
|
|
27
27
|
const markdown = header + skill;
|
|
28
28
|
return { markdown, sourceVersion: parseVersion(skill), contentHash: sha256(markdown) };
|
|
29
29
|
}
|
|
@@ -21,7 +21,7 @@ function machineChecks(m) {
|
|
|
21
21
|
}
|
|
22
22
|
else if (m.registryCache.gitState === 'behind') {
|
|
23
23
|
out.push({ id: 'machine.cli', level: 'machine', label: 'CLI', status: 'warn',
|
|
24
|
-
detail: 'cache
|
|
24
|
+
detail: 'cache out of date', remedy: cmd('awm update') });
|
|
25
25
|
}
|
|
26
26
|
else {
|
|
27
27
|
// dirty | unknown | undefined → advisory, sin acción
|
|
@@ -34,7 +34,7 @@ function machineChecks(m) {
|
|
|
34
34
|
}
|
|
35
35
|
else if (m.hook.present) {
|
|
36
36
|
out.push({ id: 'machine.hook', level: 'machine', label: 'hook SessionStart', status: 'warn',
|
|
37
|
-
detail: 'scripts
|
|
37
|
+
detail: 'incomplete scripts', remedy: cmd('awm init') });
|
|
38
38
|
}
|
|
39
39
|
else {
|
|
40
40
|
out.push({ id: 'machine.hook', level: 'machine', label: 'hook SessionStart', status: 'missing',
|
|
@@ -46,7 +46,7 @@ function machineChecks(m) {
|
|
|
46
46
|
}
|
|
47
47
|
else if (m.devCore.present) {
|
|
48
48
|
out.push({ id: 'machine.devCore', level: 'machine', label: 'dev-core (baseline)', status: 'warn',
|
|
49
|
-
detail: `${m.devCore.brokenLinks.length} symlinks
|
|
49
|
+
detail: `${m.devCore.brokenLinks.length} broken symlinks`, remedy: cmd('awm init') });
|
|
50
50
|
}
|
|
51
51
|
else {
|
|
52
52
|
out.push({ id: 'machine.devCore', level: 'machine', label: 'dev-core (baseline)', status: 'missing',
|
|
@@ -55,11 +55,11 @@ function machineChecks(m) {
|
|
|
55
55
|
// machine.globalSkills — integridad de symlinks en ~/.claude/skills (fuera del baseline)
|
|
56
56
|
const brokenGlobal = m.globalSkills.repairable.length + m.globalSkills.dead.length;
|
|
57
57
|
if (brokenGlobal === 0) {
|
|
58
|
-
out.push({ id: 'machine.globalSkills', level: 'machine', label: 'skills
|
|
58
|
+
out.push({ id: 'machine.globalSkills', level: 'machine', label: 'global skills', status: 'ok', remedy: none });
|
|
59
59
|
}
|
|
60
60
|
else {
|
|
61
|
-
out.push({ id: 'machine.globalSkills', level: 'machine', label: 'skills
|
|
62
|
-
detail: `${brokenGlobal}
|
|
61
|
+
out.push({ id: 'machine.globalSkills', level: 'machine', label: 'global skills', status: 'warn',
|
|
62
|
+
detail: `${brokenGlobal} broken links`, remedy: cmd('awm init') });
|
|
63
63
|
}
|
|
64
64
|
// machine.ambient.<b> — una fila por bundle deseado
|
|
65
65
|
for (const b of m.ambient.wanted) {
|
|
@@ -70,15 +70,15 @@ function machineChecks(m) {
|
|
|
70
70
|
// machine.context.<agent> — una fila por agente con contexto AWM gestionado
|
|
71
71
|
for (const c of m.contextInjection) {
|
|
72
72
|
if (c.state === 'injected') {
|
|
73
|
-
out.push({ id: `machine.context.${c.agent}`, level: 'machine', label: `
|
|
73
|
+
out.push({ id: `machine.context.${c.agent}`, level: 'machine', label: `AWM context (${c.agent})`,
|
|
74
74
|
status: 'ok', remedy: none });
|
|
75
75
|
}
|
|
76
76
|
else if (c.state === 'stale') {
|
|
77
|
-
out.push({ id: `machine.context.${c.agent}`, level: 'machine', label: `
|
|
78
|
-
status: 'warn', detail: '
|
|
77
|
+
out.push({ id: `machine.context.${c.agent}`, level: 'machine', label: `AWM context (${c.agent})`,
|
|
78
|
+
status: 'warn', detail: 'context out of date', remedy: cmd('awm init') });
|
|
79
79
|
}
|
|
80
80
|
else {
|
|
81
|
-
out.push({ id: `machine.context.${c.agent}`, level: 'machine', label: `
|
|
81
|
+
out.push({ id: `machine.context.${c.agent}`, level: 'machine', label: `AWM context (${c.agent})`,
|
|
82
82
|
status: 'missing', remedy: cmd('awm init') });
|
|
83
83
|
}
|
|
84
84
|
}
|
|
@@ -88,7 +88,7 @@ function projectChecks(p) {
|
|
|
88
88
|
const out = [];
|
|
89
89
|
// project.profile
|
|
90
90
|
if (p.profile.present) {
|
|
91
|
-
const exts = p.profile.extensions.length ? p.profile.extensions.join(', ') : '
|
|
91
|
+
const exts = p.profile.extensions.length ? p.profile.extensions.join(', ') : 'no extensions';
|
|
92
92
|
out.push({ id: 'project.profile', level: 'project', label: `.awm/profile.json (${exts})`,
|
|
93
93
|
status: 'ok', remedy: none });
|
|
94
94
|
}
|
|
@@ -99,21 +99,21 @@ function projectChecks(p) {
|
|
|
99
99
|
// project.activation
|
|
100
100
|
const missingLinks = p.activeBundles.expected.filter((s) => !p.activeBundles.linked.includes(s));
|
|
101
101
|
if (p.activeBundles.broken.length === 0 && missingLinks.length === 0) {
|
|
102
|
-
out.push({ id: 'project.activation', level: 'project', label: 'bundles
|
|
102
|
+
out.push({ id: 'project.activation', level: 'project', label: 'active bundles', status: 'ok', remedy: none });
|
|
103
103
|
}
|
|
104
104
|
else {
|
|
105
|
-
out.push({ id: 'project.activation', level: 'project', label: 'bundles
|
|
106
|
-
detail: `${missingLinks.length}
|
|
105
|
+
out.push({ id: 'project.activation', level: 'project', label: 'active bundles', status: 'missing',
|
|
106
|
+
detail: `${missingLinks.length} missing, ${p.activeBundles.broken.length} broken`, remedy: cmd('awm sync') });
|
|
107
107
|
}
|
|
108
108
|
// project.sensors
|
|
109
109
|
out.push(p.sensors.present
|
|
110
|
-
? { id: 'project.sensors', level: 'project', label: '
|
|
111
|
-
: { id: 'project.sensors', level: 'project', label: '
|
|
110
|
+
? { id: 'project.sensors', level: 'project', label: 'sensors', status: 'ok', remedy: none }
|
|
111
|
+
: { id: 'project.sensors', level: 'project', label: 'sensors not initialized', status: 'missing',
|
|
112
112
|
remedy: cmd('awm sensors init') });
|
|
113
113
|
// project.constitution (missing degrada; remedio agente)
|
|
114
114
|
out.push(p.constitution.present
|
|
115
115
|
? { id: 'project.constitution', level: 'project', label: 'CONSTITUTION.md', status: 'ok', remedy: none }
|
|
116
|
-
: { id: 'project.constitution', level: 'project', label: 'CONSTITUTION.md
|
|
116
|
+
: { id: 'project.constitution', level: 'project', label: 'CONSTITUTION.md missing', status: 'missing',
|
|
117
117
|
remedy: skillRemedy('project-constitution') });
|
|
118
118
|
// project.context (advisory; no degrada)
|
|
119
119
|
if (p.context.present) {
|
|
@@ -121,7 +121,7 @@ function projectChecks(p) {
|
|
|
121
121
|
status: 'ok', remedy: none });
|
|
122
122
|
}
|
|
123
123
|
else {
|
|
124
|
-
out.push({ id: 'project.context', level: 'project', label: '
|
|
124
|
+
out.push({ id: 'project.context', level: 'project', label: 'agent context (CLAUDE.md/AGENTS.md) missing', status: 'warn',
|
|
125
125
|
remedy: skillRemedy('project-context-init') });
|
|
126
126
|
}
|
|
127
127
|
return out;
|
|
@@ -6,7 +6,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.gatherContext = gatherContext;
|
|
7
7
|
// src/core/diagnostics/context.ts
|
|
8
8
|
const fs_1 = __importDefault(require("fs"));
|
|
9
|
-
const os_1 = __importDefault(require("os"));
|
|
10
9
|
const path_1 = __importDefault(require("path"));
|
|
11
10
|
const child_process_1 = require("child_process");
|
|
12
11
|
const providers_1 = require("../../providers");
|
|
@@ -16,8 +15,7 @@ const status_1 = require("../../commands/hooks/status");
|
|
|
16
15
|
const profile_1 = require("../profile");
|
|
17
16
|
const bundles_1 = require("../bundles");
|
|
18
17
|
const skill_integrity_1 = require("../skill-integrity");
|
|
19
|
-
|
|
20
|
-
function awmHome() { return process.env.AWM_HOME || path_1.default.join(home(), '.awm'); }
|
|
18
|
+
const paths_1 = require("../paths");
|
|
21
19
|
// Estado de un artefacto en <dir>/<skill>: link vivo / symlink colgante / ausente.
|
|
22
20
|
function linkState(dir, skill) {
|
|
23
21
|
const p = path_1.default.join(dir, skill);
|
|
@@ -122,7 +120,7 @@ function gatherMachine(bundles, agent = 'claude-code') {
|
|
|
122
120
|
// ambient (deseados desde ~/.awm/config.json)
|
|
123
121
|
let wanted = [];
|
|
124
122
|
try {
|
|
125
|
-
const cfg = JSON.parse(fs_1.default.readFileSync(path_1.default.join(awmHome(), 'config.json'), 'utf-8'));
|
|
123
|
+
const cfg = JSON.parse(fs_1.default.readFileSync(path_1.default.join((0, paths_1.awmHome)(), 'config.json'), 'utf-8'));
|
|
126
124
|
if (Array.isArray(cfg.ambient)) {
|
|
127
125
|
wanted = cfg.ambient.filter((x) => typeof x === 'string');
|
|
128
126
|
}
|
|
@@ -170,10 +170,10 @@ async function stepProfile(d) {
|
|
|
170
170
|
const alreadyPresent = proj.profile.extensions;
|
|
171
171
|
const newProposed = proposed.filter((p) => !alreadyPresent.includes(p));
|
|
172
172
|
if (newProposed.length === 0)
|
|
173
|
-
return bootstrapOrSkip(d, proj, '
|
|
173
|
+
return bootstrapOrSkip(d, proj, 'no new extensions');
|
|
174
174
|
const confirmed = await d.confirmExtensions(newProposed, signals);
|
|
175
175
|
if (confirmed.length === 0)
|
|
176
|
-
return bootstrapOrSkip(d, proj, '
|
|
176
|
+
return bootstrapOrSkip(d, proj, 'no extensions confirmed');
|
|
177
177
|
for (const name of confirmed) {
|
|
178
178
|
d.actions.addExtension(proj.root, name);
|
|
179
179
|
}
|
|
@@ -188,7 +188,7 @@ async function stepProfile(d) {
|
|
|
188
188
|
function bootstrapOrSkip(d, proj, skipDetail) {
|
|
189
189
|
if (!proj.profile.present) {
|
|
190
190
|
d.actions.ensureProfile(proj.root);
|
|
191
|
-
return ok('project.profile', 'project', 'applied', '
|
|
191
|
+
return ok('project.profile', 'project', 'applied', 'profile initialized (no extensions)');
|
|
192
192
|
}
|
|
193
193
|
return ok('project.profile', 'project', 'skipped', skipDetail);
|
|
194
194
|
}
|
|
@@ -239,10 +239,10 @@ function stepConstitutionInjection(d) {
|
|
|
239
239
|
return ok('project.constitutionInjection', 'project', 'skipped', 'no project');
|
|
240
240
|
const inj = (0, providers_1.getInjection)(d.agent);
|
|
241
241
|
if (!inj || inj.type !== 'config-instructions') {
|
|
242
|
-
return ok('project.constitutionInjection', 'project', 'skipped', '
|
|
242
|
+
return ok('project.constitutionInjection', 'project', 'skipped', 'covered by hook');
|
|
243
243
|
}
|
|
244
244
|
if (!proj.constitution.present) {
|
|
245
|
-
return ok('project.constitutionInjection', 'project', 'skipped', '
|
|
245
|
+
return ok('project.constitutionInjection', 'project', 'skipped', 'no CONSTITUTION.md');
|
|
246
246
|
}
|
|
247
247
|
const res = d.actions.injectProjectConstitution({ projectRoot: proj.root, agent: d.agent });
|
|
248
248
|
if (res === 'injected')
|
|
@@ -262,9 +262,9 @@ function stepContext(d) {
|
|
|
262
262
|
function stepContextInjection(d) {
|
|
263
263
|
const inj = (0, providers_1.getInjection)(d.agent);
|
|
264
264
|
if (!inj)
|
|
265
|
-
return ok('machine.contextInjection', 'machine', 'skipped', '
|
|
265
|
+
return ok('machine.contextInjection', 'machine', 'skipped', 'no injection mechanism');
|
|
266
266
|
if (inj.type === 'cc-settings-merge')
|
|
267
|
-
return ok('machine.contextInjection', 'machine', 'skipped', '
|
|
267
|
+
return ok('machine.contextInjection', 'machine', 'skipped', 'covered by hook');
|
|
268
268
|
const op = {
|
|
269
269
|
agent: d.agent,
|
|
270
270
|
scope: 'global',
|
|
@@ -0,0 +1,56 @@
|
|
|
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.WINDOWS_NATIVE_WARNING = void 0;
|
|
7
|
+
exports.homeDir = homeDir;
|
|
8
|
+
exports.awmHome = awmHome;
|
|
9
|
+
exports.platform = platform;
|
|
10
|
+
exports.isWindowsNative = isWindowsNative;
|
|
11
|
+
exports.platformLabel = platformLabel;
|
|
12
|
+
exports.warnIfUnsupportedPlatform = warnIfUnsupportedPlatform;
|
|
13
|
+
// cli/src/core/paths.ts
|
|
14
|
+
//
|
|
15
|
+
// Single source of truth for home / AWM_HOME resolution and platform detection.
|
|
16
|
+
// Functions are evaluated at CALL TIME (not require time) so env overrides are
|
|
17
|
+
// always honored and tests need no jest.resetModules().
|
|
18
|
+
const os_1 = __importDefault(require("os"));
|
|
19
|
+
const path_1 = __importDefault(require("path"));
|
|
20
|
+
/** User home directory with a robust fallback. Never returns a raw, possibly-empty process.env.HOME. */
|
|
21
|
+
function homeDir() {
|
|
22
|
+
return process.env.HOME || os_1.default.homedir();
|
|
23
|
+
}
|
|
24
|
+
/** AWM home directory (~/.awm), honoring the AWM_HOME override. */
|
|
25
|
+
function awmHome() {
|
|
26
|
+
return process.env.AWM_HOME || path_1.default.join(homeDir(), '.awm');
|
|
27
|
+
}
|
|
28
|
+
/** Raw platform string (wrapper over process.platform for testability). */
|
|
29
|
+
function platform() {
|
|
30
|
+
return process.platform;
|
|
31
|
+
}
|
|
32
|
+
/** True only on native Windows. WSL reports 'linux', so this returns false there. */
|
|
33
|
+
function isWindowsNative() {
|
|
34
|
+
return platform() === 'win32';
|
|
35
|
+
}
|
|
36
|
+
/** Human-friendly platform label for diagnostics. */
|
|
37
|
+
function platformLabel() {
|
|
38
|
+
switch (platform()) {
|
|
39
|
+
case 'win32':
|
|
40
|
+
return 'Windows (native — not supported yet, use WSL)';
|
|
41
|
+
case 'darwin':
|
|
42
|
+
return 'macOS';
|
|
43
|
+
case 'linux':
|
|
44
|
+
return 'Linux';
|
|
45
|
+
default:
|
|
46
|
+
return platform();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
exports.WINDOWS_NATIVE_WARNING = 'AWM detected native Windows. Native support is deferred; the recommended path today is WSL.\n' +
|
|
50
|
+
' Install WSL (https://learn.microsoft.com/windows/wsl/install) and run AWM inside your Linux distro.\n' +
|
|
51
|
+
' Continuing in best-effort mode, but some steps (symlinks, hooks) may not work.';
|
|
52
|
+
/** Emit the unsupported-platform warning via the provided logger, only on native Windows. */
|
|
53
|
+
function warnIfUnsupportedPlatform(log) {
|
|
54
|
+
if (isWindowsNative())
|
|
55
|
+
log(exports.WINDOWS_NATIVE_WARNING);
|
|
56
|
+
}
|
|
@@ -19,13 +19,13 @@ exports.verifyMinCliVersions = verifyMinCliVersions;
|
|
|
19
19
|
// src/core/registries.ts
|
|
20
20
|
const fs_1 = __importDefault(require("fs"));
|
|
21
21
|
const path_1 = __importDefault(require("path"));
|
|
22
|
-
const os_1 = __importDefault(require("os"));
|
|
23
22
|
const simple_git_1 = __importDefault(require("simple-git"));
|
|
24
23
|
const registry_1 = require("./registry");
|
|
25
24
|
const versioning_1 = require("./versioning");
|
|
26
25
|
const cli_version_1 = require("./cli-version");
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
const paths_1 = require("./paths");
|
|
27
|
+
// Computed at require-time by calling the shared resolver (single source of truth: core/paths.ts).
|
|
28
|
+
const AWM_HOME = (0, paths_1.awmHome)();
|
|
29
29
|
exports.REGISTRIES_DIR = path_1.default.join(AWM_HOME, 'registries');
|
|
30
30
|
exports.REGISTRIES_CONFIG_PATH = path_1.default.join(AWM_HOME, 'registries.json');
|
|
31
31
|
exports.CONTENT_DIR_NAMES = ['skills', 'bundles', 'workflows', 'agents'];
|
|
@@ -16,17 +16,16 @@ exports.offerSelfUpdate = offerSelfUpdate;
|
|
|
16
16
|
// `awm update`. AWM_NO_UPDATE_CHECK=1 desactiva ambas (tests, CI).
|
|
17
17
|
const fs_1 = __importDefault(require("fs"));
|
|
18
18
|
const path_1 = __importDefault(require("path"));
|
|
19
|
-
const os_1 = __importDefault(require("os"));
|
|
20
19
|
const child_process_1 = require("child_process");
|
|
21
20
|
const picocolors_1 = __importDefault(require("picocolors"));
|
|
22
21
|
const prompts_1 = require("@clack/prompts");
|
|
23
22
|
const cli_version_1 = require("./cli-version");
|
|
24
23
|
const versioning_1 = require("./versioning");
|
|
24
|
+
const paths_1 = require("./paths");
|
|
25
25
|
const TTL_MS = 24 * 60 * 60 * 1000;
|
|
26
26
|
const REGISTRY_URL = `https://registry.npmjs.org/${cli_version_1.CLI_PACKAGE_NAME}/latest`;
|
|
27
27
|
function cacheFile() {
|
|
28
|
-
|
|
29
|
-
return path_1.default.join(home, 'update-check.json');
|
|
28
|
+
return path_1.default.join((0, paths_1.awmHome)(), 'update-check.json');
|
|
30
29
|
}
|
|
31
30
|
function readUpdateCache() {
|
|
32
31
|
try {
|
|
@@ -72,7 +71,7 @@ function maybeNotifyUpdate(opts) {
|
|
|
72
71
|
const spawnWorker = opts?.spawnWorker ?? spawnRefreshWorker;
|
|
73
72
|
const cache = readUpdateCache();
|
|
74
73
|
if (cache?.latest && (0, versioning_1.compareSemver)(cache.latest, (0, cli_version_1.cliVersion)()) > 0) {
|
|
75
|
-
console.log(picocolors_1.default.dim(`\n⬆ awm v${cache.latest}
|
|
74
|
+
console.log(picocolors_1.default.dim(`\n⬆ awm v${cache.latest} available → npm i -g ${cli_version_1.CLI_PACKAGE_NAME}`));
|
|
76
75
|
}
|
|
77
76
|
if (!cache || now - cache.lastCheck > TTL_MS)
|
|
78
77
|
spawnWorker();
|
|
@@ -90,17 +89,17 @@ async function offerSelfUpdate(deps = {}) {
|
|
|
90
89
|
const r = await (0, prompts_1.confirm)({ message });
|
|
91
90
|
return !(0, prompts_1.isCancel)(r) && r === true;
|
|
92
91
|
});
|
|
93
|
-
const yes = await confirmImpl(
|
|
92
|
+
const yes = await confirmImpl(`Update awm v${current} → v${latest} now?`);
|
|
94
93
|
if (!yes) {
|
|
95
|
-
console.log(picocolors_1.default.dim(`
|
|
94
|
+
console.log(picocolors_1.default.dim(` To update later: npm i -g ${cli_version_1.CLI_PACKAGE_NAME}`));
|
|
96
95
|
return;
|
|
97
96
|
}
|
|
98
97
|
const runner = deps.runner ?? ((cmd, args) => (0, child_process_1.spawnSync)(cmd, args, { stdio: 'inherit', shell: true }));
|
|
99
98
|
const r = runner('npm', ['i', '-g', `${cli_version_1.CLI_PACKAGE_NAME}@latest`]);
|
|
100
99
|
if (r.status === 0) {
|
|
101
|
-
console.log(picocolors_1.default.green(` ✓ CLI
|
|
100
|
+
console.log(picocolors_1.default.green(` ✓ CLI updated to v${latest} (takes effect from the next command)`));
|
|
102
101
|
}
|
|
103
102
|
else {
|
|
104
|
-
console.warn(picocolors_1.default.yellow(` ⚠
|
|
103
|
+
console.warn(picocolors_1.default.yellow(` ⚠ Automatic update failed — run: npm i -g ${cli_version_1.CLI_PACKAGE_NAME}`));
|
|
105
104
|
}
|
|
106
105
|
}
|
package/dist/src/index.js
CHANGED
|
@@ -34,6 +34,7 @@ const registry_1 = require("./commands/registry");
|
|
|
34
34
|
const pin_1 = require("./commands/pin");
|
|
35
35
|
const profile_pins_1 = require("./core/profile-pins");
|
|
36
36
|
const update_check_1 = require("./core/update-check");
|
|
37
|
+
const paths_1 = require("./core/paths");
|
|
37
38
|
const program = new commander_1.Command();
|
|
38
39
|
program.name('awm').description('Agentic Workflow Manager').version((0, cli_version_1.cliVersion)());
|
|
39
40
|
program.hook('postAction', () => {
|
|
@@ -321,7 +322,7 @@ program.command('update')
|
|
|
321
322
|
}
|
|
322
323
|
}
|
|
323
324
|
for (const f of (0, registries_1.verifyMinCliVersions)()) {
|
|
324
|
-
console.warn(picocolors_1.default.yellow(` ⚠
|
|
325
|
+
console.warn(picocolors_1.default.yellow(` ⚠ Registry ${f.name} requires CLI ≥ ${f.min} (you have ${(0, cli_version_1.cliVersion)()}) — run: npm i -g agentic-workflow-manager`));
|
|
325
326
|
}
|
|
326
327
|
try {
|
|
327
328
|
const regen = (0, regenerate_1.regenerateGlobalContext)();
|
|
@@ -351,7 +352,7 @@ program.command('update')
|
|
|
351
352
|
}
|
|
352
353
|
catch { /* no aborta */ }
|
|
353
354
|
await (0, update_check_1.offerSelfUpdate)(); // capa 2 — Task 13
|
|
354
|
-
(0, prompts_1.outro)('✅ Registries, skills
|
|
355
|
+
(0, prompts_1.outro)('✅ Registries, skills and hooks updated.');
|
|
355
356
|
});
|
|
356
357
|
program.command('sync')
|
|
357
358
|
.description('Rebuild local skill symlinks from .awm/profile.json (e.g. after cloning on a new machine)')
|
|
@@ -359,6 +360,7 @@ program.command('sync')
|
|
|
359
360
|
.option('-m, --method <method>', 'Install method: symlink or copy', 'symlink')
|
|
360
361
|
.action(async (options) => {
|
|
361
362
|
(0, prompts_1.intro)(picocolors_1.default.bgCyan(picocolors_1.default.black(' AWM - Sync Project Profile ')));
|
|
363
|
+
(0, paths_1.warnIfUnsupportedPlatform)((m) => console.warn(picocolors_1.default.yellow(`⚠ ${m}`)));
|
|
362
364
|
const projectRoot = (0, profile_1.findProjectRoot)(process.cwd());
|
|
363
365
|
if (!projectRoot) {
|
|
364
366
|
console.error(picocolors_1.default.red('No project root found (need a .git/, package.json, or .awm/profile.json here).'));
|
|
@@ -385,8 +387,8 @@ program.command('sync')
|
|
|
385
387
|
const cliFailures = (0, registries_1.verifyMinCliVersions)();
|
|
386
388
|
if (cliFailures.length > 0) {
|
|
387
389
|
for (const f of cliFailures) {
|
|
388
|
-
console.error(picocolors_1.default.red(`
|
|
389
|
-
console.error(picocolors_1.default.red('
|
|
390
|
+
console.error(picocolors_1.default.red(`Registry ${f.name} requires CLI ≥ ${f.min} (you have ${(0, cli_version_1.cliVersion)()}).`));
|
|
391
|
+
console.error(picocolors_1.default.red(' Run: npm i -g agentic-workflow-manager'));
|
|
390
392
|
}
|
|
391
393
|
process.exit(1);
|
|
392
394
|
}
|
|
@@ -408,8 +410,8 @@ program.command('sync')
|
|
|
408
410
|
}
|
|
409
411
|
}
|
|
410
412
|
else {
|
|
411
|
-
console.error(picocolors_1.default.red(`
|
|
412
|
-
console.error(picocolors_1.default.red(`
|
|
413
|
+
console.error(picocolors_1.default.red(`This machine has ${f.name} @ ${f.actual ? `v${f.actual}` : 'HEAD (no tag)'} but the project requires v${f.required}.`));
|
|
414
|
+
console.error(picocolors_1.default.red(` Run: awm pin ${f.name} ${f.required} && awm update`));
|
|
413
415
|
}
|
|
414
416
|
}
|
|
415
417
|
process.exit(1);
|
|
@@ -8,10 +8,10 @@ exports.getTargetPath = getTargetPath;
|
|
|
8
8
|
exports.getHookConfig = getHookConfig;
|
|
9
9
|
exports.getInjection = getInjection;
|
|
10
10
|
// src/providers/index.ts
|
|
11
|
-
const os_1 = __importDefault(require("os"));
|
|
12
11
|
const path_1 = __importDefault(require("path"));
|
|
13
|
-
const
|
|
14
|
-
const
|
|
12
|
+
const paths_1 = require("../core/paths");
|
|
13
|
+
const homedir = (0, paths_1.homeDir)();
|
|
14
|
+
const awmHomeDir = (0, paths_1.awmHome)();
|
|
15
15
|
exports.PROVIDERS = {
|
|
16
16
|
antigravity: {
|
|
17
17
|
label: 'Antigravity',
|
|
@@ -38,7 +38,7 @@ exports.PROVIDERS = {
|
|
|
38
38
|
hooks: {
|
|
39
39
|
type: 'cc-settings-merge',
|
|
40
40
|
settingsPath: path_1.default.join(homedir, '.claude/settings.json'),
|
|
41
|
-
scriptsDir: path_1.default.join(
|
|
41
|
+
scriptsDir: path_1.default.join(awmHomeDir, 'hooks'),
|
|
42
42
|
matcher: 'startup|clear|compact',
|
|
43
43
|
eventName: 'SessionStart'
|
|
44
44
|
},
|
package/dist/src/utils/config.js
CHANGED
|
@@ -8,14 +8,14 @@ exports.savePreferences = savePreferences;
|
|
|
8
8
|
// src/utils/config.ts
|
|
9
9
|
const fs_1 = __importDefault(require("fs"));
|
|
10
10
|
const path_1 = __importDefault(require("path"));
|
|
11
|
-
const
|
|
11
|
+
const paths_1 = require("../core/paths");
|
|
12
12
|
const DEFAULT_PREFS = {
|
|
13
13
|
defaultAgent: 'antigravity',
|
|
14
14
|
installMethod: 'symlink',
|
|
15
15
|
defaultScope: 'local'
|
|
16
16
|
};
|
|
17
17
|
function prefsDir() {
|
|
18
|
-
return
|
|
18
|
+
return (0, paths_1.awmHome)();
|
|
19
19
|
}
|
|
20
20
|
function getPreferences() {
|
|
21
21
|
const file = path_1.default.join(prefsDir(), 'preferences.json');
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const doctor_1 = require("../../src/commands/doctor");
|
|
4
|
+
describe('doctor renderReport — platform line', () => {
|
|
5
|
+
const realPlatform = process.platform;
|
|
6
|
+
afterEach(() => {
|
|
7
|
+
Object.defineProperty(process, 'platform', { value: realPlatform, configurable: true });
|
|
8
|
+
});
|
|
9
|
+
function emptyReport() {
|
|
10
|
+
return { overall: 'healthy', hasProject: false, projectName: undefined, results: [] };
|
|
11
|
+
}
|
|
12
|
+
it('renders the platform label under the Machine header', () => {
|
|
13
|
+
Object.defineProperty(process, 'platform', { value: 'linux', configurable: true });
|
|
14
|
+
const out = (0, doctor_1.renderReport)(emptyReport());
|
|
15
|
+
expect(out).toContain('platform: Linux');
|
|
16
|
+
});
|
|
17
|
+
it('flags native Windows with a WSL hint', () => {
|
|
18
|
+
Object.defineProperty(process, 'platform', { value: 'win32', configurable: true });
|
|
19
|
+
const out = (0, doctor_1.renderReport)(emptyReport());
|
|
20
|
+
expect(out).toContain('WSL');
|
|
21
|
+
});
|
|
22
|
+
});
|
|
@@ -22,27 +22,27 @@ function report(partial = {}) {
|
|
|
22
22
|
describe('renderReport', () => {
|
|
23
23
|
it('renders the machine block with glyphs and remedies', () => {
|
|
24
24
|
const out = (0, doctor_1.renderReport)(report());
|
|
25
|
-
expect(out).toContain('AWM ·
|
|
26
|
-
expect(out).toContain('
|
|
25
|
+
expect(out).toContain('AWM · harness status');
|
|
26
|
+
expect(out).toContain('Machine (global)');
|
|
27
27
|
expect(out).toContain('✔ CLI v1.0.0');
|
|
28
28
|
expect(out).toContain('✖ hook SessionStart');
|
|
29
29
|
expect(out).toContain('→ awm init');
|
|
30
|
-
expect(out).toContain('
|
|
30
|
+
expect(out).toContain('status: degraded · 1 suggested actions');
|
|
31
31
|
});
|
|
32
32
|
it('omits the project block and shows a hint when hasProject is false', () => {
|
|
33
33
|
const out = (0, doctor_1.renderReport)(report());
|
|
34
|
-
expect(out).toContain('(
|
|
34
|
+
expect(out).toContain('(no project in cwd)');
|
|
35
35
|
expect(out).not.toContain('Proyecto:');
|
|
36
36
|
});
|
|
37
37
|
it('renders the detail field in parentheses when present', () => {
|
|
38
38
|
const out = (0, doctor_1.renderReport)(report({
|
|
39
39
|
results: [
|
|
40
40
|
{ id: 'machine.cli', level: 'machine', label: 'CLI v1.2.0', status: 'warn',
|
|
41
|
-
detail: 'cache
|
|
41
|
+
detail: 'cache out of date', remedy: { kind: 'command', value: 'awm update' } },
|
|
42
42
|
],
|
|
43
43
|
}));
|
|
44
44
|
expect(out).toContain('CLI v1.2.0');
|
|
45
|
-
expect(out).toContain('(cache
|
|
45
|
+
expect(out).toContain('(cache out of date)');
|
|
46
46
|
expect(out).toContain('→ awm update');
|
|
47
47
|
});
|
|
48
48
|
it('renders the project block titled with projectName', () => {
|
|
@@ -50,11 +50,11 @@ describe('renderReport', () => {
|
|
|
50
50
|
hasProject: true,
|
|
51
51
|
projectName: 'belanz',
|
|
52
52
|
results: [
|
|
53
|
-
{ id: 'project.constitution', level: 'project', label: 'CONSTITUTION.md
|
|
53
|
+
{ id: 'project.constitution', level: 'project', label: 'CONSTITUTION.md missing',
|
|
54
54
|
status: 'missing', remedy: { kind: 'skill', value: 'project-constitution' } },
|
|
55
55
|
],
|
|
56
56
|
}));
|
|
57
|
-
expect(out).toContain('
|
|
57
|
+
expect(out).toContain('Project: belanz');
|
|
58
58
|
expect(out).toContain('→ skill: project-constitution');
|
|
59
59
|
});
|
|
60
60
|
});
|
|
@@ -0,0 +1,59 @@
|
|
|
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
|
+
const fs_1 = __importDefault(require("fs"));
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const os_1 = __importDefault(require("os"));
|
|
9
|
+
describe('hooks/install — skill symlink fallback to copy', () => {
|
|
10
|
+
let tmpHome;
|
|
11
|
+
let origHome;
|
|
12
|
+
let origAwmHome;
|
|
13
|
+
let symlinkSpy;
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
tmpHome = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-symlink-fb-'));
|
|
16
|
+
origHome = process.env.HOME;
|
|
17
|
+
origAwmHome = process.env.AWM_HOME;
|
|
18
|
+
process.env.HOME = tmpHome;
|
|
19
|
+
process.env.AWM_HOME = path_1.default.join(tmpHome, '.awm');
|
|
20
|
+
jest.resetModules();
|
|
21
|
+
});
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
symlinkSpy?.mockRestore();
|
|
24
|
+
fs_1.default.rmSync(tmpHome, { recursive: true, force: true });
|
|
25
|
+
if (origHome === undefined)
|
|
26
|
+
delete process.env.HOME;
|
|
27
|
+
else
|
|
28
|
+
process.env.HOME = origHome;
|
|
29
|
+
if (origAwmHome === undefined)
|
|
30
|
+
delete process.env.AWM_HOME;
|
|
31
|
+
else
|
|
32
|
+
process.env.AWM_HOME = origAwmHome;
|
|
33
|
+
});
|
|
34
|
+
function seedRegistry(root) {
|
|
35
|
+
const hooksDir = path_1.default.join(root, 'hooks');
|
|
36
|
+
const skillDir = path_1.default.join(root, 'skills', 'using-awm');
|
|
37
|
+
fs_1.default.mkdirSync(hooksDir, { recursive: true });
|
|
38
|
+
fs_1.default.mkdirSync(skillDir, { recursive: true });
|
|
39
|
+
fs_1.default.writeFileSync(path_1.default.join(hooksDir, 'session-start'), '#!/bin/sh\n');
|
|
40
|
+
fs_1.default.writeFileSync(path_1.default.join(hooksDir, 'run-hook.cmd'), '#!/bin/sh\n');
|
|
41
|
+
fs_1.default.writeFileSync(path_1.default.join(skillDir, 'SKILL.md'), '# using-awm\n');
|
|
42
|
+
}
|
|
43
|
+
it('copies the skill when symlink throws (EPERM), preserving content', () => {
|
|
44
|
+
const registryRoot = path_1.default.join(tmpHome, 'registry');
|
|
45
|
+
seedRegistry(registryRoot);
|
|
46
|
+
// Force symlinkSync to fail like a platform without symlink permission.
|
|
47
|
+
symlinkSpy = jest.spyOn(fs_1.default, 'symlinkSync').mockImplementation(() => {
|
|
48
|
+
const err = new Error('EPERM: operation not permitted, symlink');
|
|
49
|
+
err.code = 'EPERM';
|
|
50
|
+
throw err;
|
|
51
|
+
});
|
|
52
|
+
const { installHook } = require('../../../src/commands/hooks/install');
|
|
53
|
+
const result = installHook({ agent: 'claude-code', registryRoot, installMethod: 'copy' });
|
|
54
|
+
const skillDest = path_1.default.join(result.scriptsDir, 'using-awm.md');
|
|
55
|
+
expect(fs_1.default.existsSync(skillDest)).toBe(true);
|
|
56
|
+
expect(fs_1.default.lstatSync(skillDest).isSymbolicLink()).toBe(false); // it was copied, not linked
|
|
57
|
+
expect(fs_1.default.readFileSync(skillDest, 'utf-8')).toContain('using-awm');
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -35,13 +35,13 @@ describe('renderInitOutcome', () => {
|
|
|
35
35
|
};
|
|
36
36
|
const out = (0, init_1.renderInitOutcome)(outcome);
|
|
37
37
|
expect(out).toContain('AWM · init');
|
|
38
|
-
expect(out).toContain('
|
|
39
|
-
expect(out).toContain('
|
|
38
|
+
expect(out).toContain('Initial state');
|
|
39
|
+
expect(out).toContain('Actions');
|
|
40
40
|
expect(out).toContain('machine.cache');
|
|
41
41
|
expect(out).toContain('skill: project-constitution');
|
|
42
|
-
expect(out).toContain('
|
|
43
|
-
expect(out).toContain('AWM ·
|
|
44
|
-
expect(out).toContain('1
|
|
42
|
+
expect(out).toContain('Final state');
|
|
43
|
+
expect(out).toContain('AWM · harness status'); // viene de renderReport
|
|
44
|
+
expect(out).toContain('1 steps require an agent');
|
|
45
45
|
});
|
|
46
46
|
});
|
|
47
47
|
describe('runInit', () => {
|
|
@@ -0,0 +1,34 @@
|
|
|
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
|
+
const path_1 = __importDefault(require("path"));
|
|
7
|
+
const eslint_1 = require("../../../src/commands/sensors/formatters/eslint");
|
|
8
|
+
describe('parseEslintOutput — relative path normalization', () => {
|
|
9
|
+
it('produces a cwd-relative path using the OS separator', () => {
|
|
10
|
+
const cwd = process.cwd();
|
|
11
|
+
const abs = path_1.default.join(cwd, 'src', 'foo.ts');
|
|
12
|
+
const raw = JSON.stringify([
|
|
13
|
+
{ filePath: abs, messages: [{ ruleId: 'no-eval', severity: 2, message: 'no eval', line: 3, column: 1 }] },
|
|
14
|
+
]);
|
|
15
|
+
const errors = (0, eslint_1.parseEslintOutput)(raw);
|
|
16
|
+
expect(errors).toHaveLength(1);
|
|
17
|
+
const first = errors[0];
|
|
18
|
+
if (!first)
|
|
19
|
+
throw new Error('Expected at least one error');
|
|
20
|
+
// path.join('src','foo.ts') uses the OS separator; on POSIX this is 'src/foo.ts'
|
|
21
|
+
expect(first.file).toBe(path_1.default.join('src', 'foo.ts'));
|
|
22
|
+
const file = first.file ?? '';
|
|
23
|
+
expect(file.startsWith(path_1.default.sep)).toBe(false); // genuinely relative
|
|
24
|
+
});
|
|
25
|
+
it('preserves the absolute path for files outside cwd', () => {
|
|
26
|
+
const abs = '/some/other/project/file.ts';
|
|
27
|
+
const raw = JSON.stringify([
|
|
28
|
+
{ filePath: abs, messages: [{ ruleId: 'no-eval', severity: 2, message: 'no eval', line: 1, column: 1 }] },
|
|
29
|
+
]);
|
|
30
|
+
const errors = (0, eslint_1.parseEslintOutput)(raw);
|
|
31
|
+
expect(errors).toHaveLength(1);
|
|
32
|
+
expect(errors[0]?.file).toBe(abs); // absolute path preserved, no traversal strings
|
|
33
|
+
});
|
|
34
|
+
});
|
|
@@ -50,7 +50,7 @@ describe('computeSensorStatus', () => {
|
|
|
50
50
|
const result = (0, status_1.computeSensorStatus)(tmpDir);
|
|
51
51
|
expect(result.overall).toBe('DEGRADED');
|
|
52
52
|
expect(result.checks.depcheck.ok).toBe(false);
|
|
53
|
-
expect(result.checks.depcheck.detail).toMatch(/
|
|
53
|
+
expect(result.checks.depcheck.detail).toMatch(/not installed locally/i);
|
|
54
54
|
});
|
|
55
55
|
it('marks a sensor DEGRADED when its --config file is missing', () => {
|
|
56
56
|
installLocalBin('eslint');
|
|
@@ -61,7 +61,7 @@ describe('computeSensorStatus', () => {
|
|
|
61
61
|
}));
|
|
62
62
|
const result = (0, status_1.computeSensorStatus)(tmpDir);
|
|
63
63
|
expect(result.checks.lint.ok).toBe(false);
|
|
64
|
-
expect(result.checks.lint.detail).toMatch(/config
|
|
64
|
+
expect(result.checks.lint.detail).toMatch(/missing config/i);
|
|
65
65
|
});
|
|
66
66
|
it('is HEALTHY when the npx tool is installed and the --config file exists', () => {
|
|
67
67
|
installLocalBin('eslint');
|
|
@@ -138,7 +138,7 @@ describe('runChecks — project', () => {
|
|
|
138
138
|
const report = (0, checks_1.runChecks)({ machine: healthyMachine(), project: p });
|
|
139
139
|
const c = report.results.find((r) => r.id === 'project.context');
|
|
140
140
|
expect(c.status).toBe('warn');
|
|
141
|
-
expect(c.label).toBe('
|
|
141
|
+
expect(c.label).toBe('agent context (CLAUDE.md/AGENTS.md) missing');
|
|
142
142
|
expect(c.remedy).toEqual({ kind: 'skill', value: 'project-context-init' });
|
|
143
143
|
expect(report.overall).toBe('healthy');
|
|
144
144
|
});
|
|
@@ -0,0 +1,78 @@
|
|
|
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
|
+
const os_1 = __importDefault(require("os"));
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const paths_1 = require("../../src/core/paths");
|
|
9
|
+
describe('core/paths', () => {
|
|
10
|
+
let origHome;
|
|
11
|
+
let origAwmHome;
|
|
12
|
+
const realPlatform = process.platform;
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
origHome = process.env.HOME;
|
|
15
|
+
origAwmHome = process.env.AWM_HOME;
|
|
16
|
+
});
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
if (origHome === undefined)
|
|
19
|
+
delete process.env.HOME;
|
|
20
|
+
else
|
|
21
|
+
process.env.HOME = origHome;
|
|
22
|
+
if (origAwmHome === undefined)
|
|
23
|
+
delete process.env.AWM_HOME;
|
|
24
|
+
else
|
|
25
|
+
process.env.AWM_HOME = origAwmHome;
|
|
26
|
+
Object.defineProperty(process, 'platform', { value: realPlatform, configurable: true });
|
|
27
|
+
});
|
|
28
|
+
function setPlatform(p) {
|
|
29
|
+
Object.defineProperty(process, 'platform', { value: p, configurable: true });
|
|
30
|
+
}
|
|
31
|
+
it('homeDir uses process.env.HOME when set', () => {
|
|
32
|
+
process.env.HOME = '/tmp/fake-home';
|
|
33
|
+
expect((0, paths_1.homeDir)()).toBe('/tmp/fake-home');
|
|
34
|
+
});
|
|
35
|
+
it('homeDir falls back to os.homedir() when HOME is unset', () => {
|
|
36
|
+
delete process.env.HOME;
|
|
37
|
+
expect((0, paths_1.homeDir)()).toBe(os_1.default.homedir());
|
|
38
|
+
});
|
|
39
|
+
it('awmHome honors AWM_HOME override', () => {
|
|
40
|
+
process.env.AWM_HOME = '/tmp/custom-awm';
|
|
41
|
+
expect((0, paths_1.awmHome)()).toBe('/tmp/custom-awm');
|
|
42
|
+
});
|
|
43
|
+
it('awmHome defaults to <home>/.awm when AWM_HOME is unset', () => {
|
|
44
|
+
delete process.env.AWM_HOME;
|
|
45
|
+
process.env.HOME = '/tmp/fake-home';
|
|
46
|
+
expect((0, paths_1.awmHome)()).toBe(path_1.default.join('/tmp/fake-home', '.awm'));
|
|
47
|
+
});
|
|
48
|
+
it('platform reflects process.platform', () => {
|
|
49
|
+
setPlatform('linux');
|
|
50
|
+
expect((0, paths_1.platform)()).toBe('linux');
|
|
51
|
+
});
|
|
52
|
+
it('isWindowsNative is true only on win32', () => {
|
|
53
|
+
setPlatform('win32');
|
|
54
|
+
expect((0, paths_1.isWindowsNative)()).toBe(true);
|
|
55
|
+
setPlatform('linux');
|
|
56
|
+
expect((0, paths_1.isWindowsNative)()).toBe(false);
|
|
57
|
+
setPlatform('darwin');
|
|
58
|
+
expect((0, paths_1.isWindowsNative)()).toBe(false);
|
|
59
|
+
});
|
|
60
|
+
it('platformLabel describes each known platform', () => {
|
|
61
|
+
setPlatform('linux');
|
|
62
|
+
expect((0, paths_1.platformLabel)()).toBe('Linux');
|
|
63
|
+
setPlatform('darwin');
|
|
64
|
+
expect((0, paths_1.platformLabel)()).toBe('macOS');
|
|
65
|
+
setPlatform('win32');
|
|
66
|
+
expect((0, paths_1.platformLabel)()).toContain('WSL');
|
|
67
|
+
});
|
|
68
|
+
it('warnIfUnsupportedPlatform calls the logger only on win32', () => {
|
|
69
|
+
const calls = [];
|
|
70
|
+
const log = (m) => calls.push(m);
|
|
71
|
+
setPlatform('linux');
|
|
72
|
+
(0, paths_1.warnIfUnsupportedPlatform)(log);
|
|
73
|
+
expect(calls).toHaveLength(0);
|
|
74
|
+
setPlatform('win32');
|
|
75
|
+
(0, paths_1.warnIfUnsupportedPlatform)(log);
|
|
76
|
+
expect(calls).toEqual([paths_1.WINDOWS_NATIVE_WARNING]);
|
|
77
|
+
});
|
|
78
|
+
});
|
package/package.json
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentic-workflow-manager",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"main": "dist/src/index.js",
|
|
5
5
|
"bin": {
|
|
6
6
|
"awm": "./dist/src/index.js"
|
|
7
7
|
},
|
|
8
|
-
"files": [
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
9
11
|
"repository": {
|
|
10
12
|
"type": "git",
|
|
11
13
|
"url": "git+https://github.com/Kodria/agentic-workflow.git",
|