agentic-workflow-manager 9.19.0 → 9.20.1

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.
Files changed (68) hide show
  1. package/dist/src/commands/change/index.js +492 -0
  2. package/dist/src/commands/changes/index.js +84 -0
  3. package/dist/src/commands/sensors/index.js +9 -0
  4. package/dist/src/commands/sensors/run.js +1 -0
  5. package/dist/src/core/changes/command.js +38 -0
  6. package/dist/src/core/changes/derive.js +120 -0
  7. package/dist/src/core/changes/fs-safe.js +47 -0
  8. package/dist/src/core/changes/git-env.js +68 -0
  9. package/dist/src/core/changes/git.js +216 -0
  10. package/dist/src/core/changes/list-parse.js +67 -0
  11. package/dist/src/core/changes/list.js +408 -0
  12. package/dist/src/core/changes/parse.js +72 -0
  13. package/dist/src/core/changes/range.js +53 -0
  14. package/dist/src/core/changes/receipt.js +274 -0
  15. package/dist/src/core/changes/record-io.js +149 -0
  16. package/dist/src/core/changes/render-list.js +109 -0
  17. package/dist/src/core/changes/render.js +178 -0
  18. package/dist/src/core/changes/text-safety.js +116 -0
  19. package/dist/src/core/changes/trailers.js +76 -0
  20. package/dist/src/core/changes/types.js +26 -0
  21. package/dist/src/core/changes/validate-shape.js +219 -0
  22. package/dist/src/core/changes/worktree.js +53 -0
  23. package/dist/src/core/process/model.js +153 -10
  24. package/dist/src/index.js +4 -0
  25. package/dist/src/release/orchestrator.js +3 -1
  26. package/dist/tests/commands/change/new.test.js +1126 -0
  27. package/dist/tests/commands/change/validate.test.js +1145 -0
  28. package/dist/tests/commands/changes/list.test.js +423 -0
  29. package/dist/tests/commands/process.test.js +4 -0
  30. package/dist/tests/commands/sensors/index.test.js +1 -1
  31. package/dist/tests/core/changes/derive.test.js +455 -0
  32. package/dist/tests/core/changes/fs-safe.test.js +82 -0
  33. package/dist/tests/core/changes/git-env.test.js +143 -0
  34. package/dist/tests/core/changes/git.test.js +442 -0
  35. package/dist/tests/core/changes/helpers.js +269 -0
  36. package/dist/tests/core/changes/list-fixtures.js +122 -0
  37. package/dist/tests/core/changes/list-long-history.test.js +52 -0
  38. package/dist/tests/core/changes/list-paging.test.js +511 -0
  39. package/dist/tests/core/changes/list-parse.test.js +120 -0
  40. package/dist/tests/core/changes/list-tree.test.js +44 -0
  41. package/dist/tests/core/changes/list.test.js +821 -0
  42. package/dist/tests/core/changes/parse.test.js +142 -0
  43. package/dist/tests/core/changes/predicates.test.js +83 -0
  44. package/dist/tests/core/changes/range.test.js +92 -0
  45. package/dist/tests/core/changes/receipt.test.js +947 -0
  46. package/dist/tests/core/changes/record-io.test.js +307 -0
  47. package/dist/tests/core/changes/record.test.js +549 -0
  48. package/dist/tests/core/changes/render-list.test.js +212 -0
  49. package/dist/tests/core/changes/render.test.js +202 -0
  50. package/dist/tests/core/changes/text-safety.test.js +226 -0
  51. package/dist/tests/core/changes/trailers.test.js +181 -0
  52. package/dist/tests/core/changes/worktree.test.js +259 -0
  53. package/dist/tests/core/dashboard/write-html.test.js +2 -3
  54. package/dist/tests/core/evidence/store.test.js +2 -3
  55. package/dist/tests/core/ledger/store.test.js +3 -9
  56. package/dist/tests/core/process/discover.test.js +21 -0
  57. package/dist/tests/core/process/model.test.js +338 -0
  58. package/dist/tests/helpers/platform.js +27 -0
  59. package/dist/tests/release/orchestrator-trailer.test.js +72 -0
  60. package/dist/tests/release/orchestrator.test.js +1 -0
  61. package/dist/tests/structural/change-commands-are-registered.test.js +29 -0
  62. package/dist/tests/structural/change-record-git-and-platform-conventions.test.js +78 -0
  63. package/dist/tests/structural/process-model-description-contract.test.js +34 -0
  64. package/package.json +1 -1
  65. package/prebuilds/darwin-arm64/secure_fs.node +0 -0
  66. package/prebuilds/darwin-x64/secure_fs.node +0 -0
  67. package/prebuilds/win32-arm64/secure_fs.node +0 -0
  68. package/prebuilds/win32-x64/secure_fs.node +0 -0
@@ -0,0 +1,259 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ const fs_1 = __importDefault(require("fs"));
40
+ const os_1 = __importDefault(require("os"));
41
+ const path_1 = __importDefault(require("path"));
42
+ const child_process_1 = __importStar(require("child_process"));
43
+ const helpers_1 = require("./helpers");
44
+ const worktree_1 = require("../../../src/core/changes/worktree");
45
+ const ENV_KEYS = ['HOME', 'AWM_HOME', 'GIT_CONFIG_GLOBAL', 'GIT_CONFIG_NOSYSTEM'];
46
+ const ORIGINAL = Object.fromEntries(ENV_KEYS.map((k) => [k, process.env[k]]));
47
+ let saved = {};
48
+ let root;
49
+ let home;
50
+ // Jest's sandboxed `process.env` is a copy that children do not see, so the child's environment is passed explicitly.
51
+ const gitEnv = () => ({ ...process.env, HOME: home, GIT_CONFIG_GLOBAL: path_1.default.join(home, 'gitconfig'), GIT_AUTHOR_NAME: 't', GIT_AUTHOR_EMAIL: 't@t', GIT_COMMITTER_NAME: 't', GIT_COMMITTER_EMAIL: 't@t', GIT_CONFIG_NOSYSTEM: '1' });
52
+ const g = (...args) => (0, child_process_1.execFileSync)('git', ['-c', 'commit.gpgsign=false', ...args], { cwd: root, encoding: 'utf8', env: gitEnv() }).trim();
53
+ const real = child_process_1.default.execFileSync;
54
+ let spy;
55
+ beforeEach(() => {
56
+ saved = Object.fromEntries(ENV_KEYS.map((k) => [k, process.env[k]]));
57
+ home = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-home-'));
58
+ Object.assign(process.env, { HOME: home, AWM_HOME: path_1.default.join(home, '.awm'), GIT_CONFIG_GLOBAL: (0, helpers_1.emptyGitConfig)(home), GIT_CONFIG_NOSYSTEM: '1' });
59
+ root = fs_1.default.realpathSync.native(fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-wt-')));
60
+ g('init', '-q', '-b', 'main');
61
+ fs_1.default.writeFileSync(path_1.default.join(root, 'a.txt'), 'a');
62
+ g('add', '.');
63
+ g('commit', '-q', '-m', 'feat: a');
64
+ });
65
+ afterEach(() => {
66
+ spy?.mockRestore();
67
+ spy = undefined;
68
+ for (const [k, v] of Object.entries(saved)) {
69
+ if (v === undefined)
70
+ delete process.env[k];
71
+ else
72
+ process.env[k] = v;
73
+ }
74
+ fs_1.default.rmSync(root, { recursive: true, force: true });
75
+ fs_1.default.rmSync(home, { recursive: true, force: true });
76
+ });
77
+ afterAll(() => {
78
+ // The environment the suite started with is back, so nothing leaks into the next test file.
79
+ for (const k of ENV_KEYS)
80
+ expect(process.env[k]).toBe(ORIGINAL[k]);
81
+ });
82
+ /** Intercepts git invocations whose first argument is `first`; every other call reaches the real git. */
83
+ const intercept = (first, handler) => {
84
+ spy = jest.spyOn(child_process_1.default, 'execFileSync').mockImplementation(((cmd, args, opts) => {
85
+ if (cmd === 'git' && args[0] === first)
86
+ return handler(opts, args);
87
+ return real(cmd, args, opts);
88
+ }));
89
+ };
90
+ const fail = (code) => () => { throw Object.assign(new Error(`spawnSync git ${code}`), { code }); };
91
+ describe('worktree state', () => {
92
+ test('R2.x isolation: HOME and AWM_HOME point at a throwaway directory, never the real ones', () => {
93
+ expect(process.env.HOME).toBe(home);
94
+ expect(process.env.HOME).not.toBe(ORIGINAL.HOME);
95
+ expect(process.env.AWM_HOME).toBe(path_1.default.join(home, '.awm'));
96
+ expect(process.env.AWM_HOME).not.toBe(ORIGINAL.AWM_HOME);
97
+ expect(process.env.GIT_CONFIG_GLOBAL).toBe(path_1.default.join(home, 'gitconfig'));
98
+ expect(fs_1.default.readFileSync(path_1.default.join(home, 'gitconfig'), 'utf8')).toBe('');
99
+ expect(process.env.GIT_CONFIG_NOSYSTEM).toBe('1');
100
+ });
101
+ test('the runtime-state exclusion list is exactly the local state paths', () => {
102
+ expect([...worktree_1.RUNTIME_STATE_EXCLUDES]).toEqual(['.awm/sensors/runs', '.awm/journal', '.awm/ledger', '.awm/evidence', '.awm/track.json']);
103
+ });
104
+ test('a clean repository reports its root, HEAD and a clean tree', () => {
105
+ const state = (0, worktree_1.gitState)(root, true);
106
+ expect(state.kind).toBe('ok');
107
+ if (state.kind === 'ok') {
108
+ expect(fs_1.default.realpathSync.native(state.root)).toBe(fs_1.default.realpathSync.native(root));
109
+ expect(state.commit).toBe(g('rev-parse', 'HEAD'));
110
+ expect(state.dirty).toBe(false);
111
+ }
112
+ });
113
+ test('the root is normalized with native separators', () => {
114
+ intercept('rev-parse', (opts, args) => (args[1] === '--show-toplevel' ? `${root}${path_1.default.sep}.${path_1.default.sep}\n` : real('git', args, opts)));
115
+ const state = (0, worktree_1.gitState)(root, true);
116
+ expect(state.kind === 'ok' && state.root).toBe(path_1.default.resolve(root));
117
+ });
118
+ test('an untracked file is dirty for the pre-run measure and clean for the post-run recheck', () => {
119
+ fs_1.default.writeFileSync(path_1.default.join(root, 'new.ts'), 'x');
120
+ const pre = (0, worktree_1.gitState)(root, true);
121
+ const post = (0, worktree_1.gitState)(root, false);
122
+ expect(pre.kind === 'ok' && pre.dirty).toBe(true);
123
+ expect(post.kind === 'ok' && post.dirty).toBe(false);
124
+ });
125
+ test('the pre-run measure sees untracked files even when status.showUntrackedFiles is no', () => {
126
+ g('config', 'status.showUntrackedFiles', 'no');
127
+ fs_1.default.writeFileSync(path_1.default.join(root, 'new.ts'), 'x');
128
+ const pre = (0, worktree_1.gitState)(root, true);
129
+ expect(pre.kind === 'ok' && pre.dirty).toBe(true);
130
+ });
131
+ test('a tracked change is dirty for both measures', () => {
132
+ fs_1.default.writeFileSync(path_1.default.join(root, 'a.txt'), 'changed');
133
+ const pre = (0, worktree_1.gitState)(root, true);
134
+ const post = (0, worktree_1.gitState)(root, false);
135
+ expect(pre.kind === 'ok' && pre.dirty).toBe(true);
136
+ expect(post.kind === 'ok' && post.dirty).toBe(true);
137
+ });
138
+ test.each(worktree_1.RUNTIME_STATE_EXCLUDES.map((p) => [p]))('an untracked file at %s is not a change', (rel) => {
139
+ fs_1.default.mkdirSync(path_1.default.dirname(path_1.default.join(root, rel)), { recursive: true });
140
+ fs_1.default.writeFileSync(path_1.default.join(root, rel), 'x');
141
+ const pre = (0, worktree_1.gitState)(root, true);
142
+ expect(pre.kind === 'ok' && pre.dirty).toBe(false);
143
+ });
144
+ test('extra exclusions hide changes under them, on top of the runtime-state ones', () => {
145
+ fs_1.default.mkdirSync(path_1.default.join(root, 'docs/changes'), { recursive: true });
146
+ fs_1.default.writeFileSync(path_1.default.join(root, 'docs/changes/r.md'), 'r');
147
+ const without = (0, worktree_1.gitState)(root, true);
148
+ const withExtra = (0, worktree_1.gitState)(root, true, ['docs/changes']);
149
+ expect(without.kind === 'ok' && without.dirty).toBe(true);
150
+ expect(withExtra.kind === 'ok' && withExtra.dirty).toBe(false);
151
+ fs_1.default.writeFileSync(path_1.default.join(root, 'other.ts'), 'x');
152
+ const stillDirty = (0, worktree_1.gitState)(root, true, ['docs/changes']);
153
+ expect(stillDirty.kind === 'ok' && stillDirty.dirty).toBe(true);
154
+ });
155
+ test('extra exclusions keep the runtime-state exclusions', () => {
156
+ fs_1.default.mkdirSync(path_1.default.join(root, '.awm/journal'), { recursive: true });
157
+ fs_1.default.writeFileSync(path_1.default.join(root, '.awm/journal/j.json'), 'x');
158
+ const state = (0, worktree_1.gitState)(root, true, ['docs/changes']);
159
+ expect(state.kind === 'ok' && state.dirty).toBe(false);
160
+ });
161
+ test.each([['docs/changes'], [[7]], [['../out']], [['a\nb']], [['/abs']], [[' pad']], [Array.from({ length: 65 }, (_, i) => `d${i}`)]])('unsafe extra exclusions %j are a failure, never a throw', (extra) => {
162
+ expect((0, worktree_1.gitState)(root, true, extra)).toEqual({ kind: 'failed' });
163
+ });
164
+ test('GIT_*_PATHSPECS variables in the environment do not change the exclusions', () => {
165
+ fs_1.default.mkdirSync(path_1.default.join(root, 'docs/changes'), { recursive: true });
166
+ fs_1.default.writeFileSync(path_1.default.join(root, 'docs/changes/r.md'), 'r');
167
+ fs_1.default.mkdirSync(path_1.default.join(root, '.awm/journal'), { recursive: true });
168
+ fs_1.default.writeFileSync(path_1.default.join(root, '.awm/journal/j.json'), 'x');
169
+ const saved = process.env.GIT_LITERAL_PATHSPECS;
170
+ process.env.GIT_LITERAL_PATHSPECS = '1';
171
+ try {
172
+ const state = (0, worktree_1.gitState)(root, true, ['docs/changes']);
173
+ expect(state.kind === 'ok' && state.dirty).toBe(false);
174
+ }
175
+ finally {
176
+ if (saved === undefined)
177
+ delete process.env.GIT_LITERAL_PATHSPECS;
178
+ else
179
+ process.env.GIT_LITERAL_PATHSPECS = saved;
180
+ }
181
+ });
182
+ describe('a submodule change is a change whatever the configuration says', () => {
183
+ // Offline: vendor/sm is a nested repository registered as a gitlink, hidden by .gitmodules `ignore = all` and diff.ignoreSubmodules=all.
184
+ let sm = '';
185
+ let commitInSm = () => '';
186
+ const registerHiddenSubmodule = () => {
187
+ const hidden = (0, helpers_1.makeHiddenSubmodule)(root, (cwd, ...args) => (0, child_process_1.execFileSync)('git', ['-c', 'commit.gpgsign=false', ...args], { cwd, encoding: 'utf8', env: gitEnv() }).trim());
188
+ sm = hidden.dir;
189
+ commitInSm = hidden.commitInSm;
190
+ };
191
+ test('a clean tree holding a submodule is clean', () => {
192
+ registerHiddenSubmodule();
193
+ const state = (0, worktree_1.gitState)(root, true);
194
+ expect(state.kind === 'ok' && state.dirty).toBe(false);
195
+ });
196
+ test('an untracked file inside a clean submodule: the post-run recheck ignores it, the pre-run measure counts it', () => {
197
+ registerHiddenSubmodule();
198
+ fs_1.default.writeFileSync(path_1.default.join(sm, 'coverage.out'), 'x'); // what a sensor leaves behind
199
+ const post = (0, worktree_1.gitState)(root, false);
200
+ const pre = (0, worktree_1.gitState)(root, true);
201
+ expect(post.kind === 'ok' && post.dirty).toBe(false);
202
+ // git reports the submodule itself as modified (untracked content) in the normal-untracked mode, so the pre-run measure is dirty.
203
+ expect(pre.kind === 'ok' && pre.dirty).toBe(true);
204
+ });
205
+ test.each([[true], [false]])('modified tracked content inside a submodule makes the tree dirty (untracked=%p)', (untracked) => {
206
+ registerHiddenSubmodule();
207
+ fs_1.default.writeFileSync(path_1.default.join(sm, 'one'), 'changed');
208
+ const state = (0, worktree_1.gitState)(root, untracked);
209
+ expect(state.kind === 'ok' && state.dirty).toBe(true);
210
+ });
211
+ test.each([[true], [false]])('a submodule checked out at another commit than the gitlink makes the tree dirty (untracked=%p)', (untracked) => {
212
+ registerHiddenSubmodule();
213
+ commitInSm('two');
214
+ const state = (0, worktree_1.gitState)(root, untracked);
215
+ expect(state.kind === 'ok' && state.dirty).toBe(true);
216
+ });
217
+ });
218
+ test('a non-string or empty cwd is a failure, never a throw', () => {
219
+ expect((0, worktree_1.gitState)(undefined, true)).toEqual({ kind: 'failed' });
220
+ expect((0, worktree_1.gitState)('', true)).toEqual({ kind: 'failed' });
221
+ });
222
+ test('a directory outside git is not-repo', () => {
223
+ const plain = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-plain-'));
224
+ try {
225
+ expect((0, worktree_1.gitState)(plain, true)).toEqual({ kind: 'not-repo' });
226
+ }
227
+ finally {
228
+ fs_1.default.rmSync(plain, { recursive: true, force: true });
229
+ }
230
+ });
231
+ test('a repository without a commit is not-repo', () => {
232
+ const empty = fs_1.default.realpathSync.native(fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-empty-')));
233
+ try {
234
+ (0, child_process_1.execFileSync)('git', ['init', '-q', '-b', 'main'], { cwd: empty, env: gitEnv() });
235
+ expect((0, worktree_1.gitState)(empty, true)).toEqual({ kind: 'not-repo' });
236
+ }
237
+ finally {
238
+ fs_1.default.rmSync(empty, { recursive: true, force: true });
239
+ }
240
+ });
241
+ test.each([['ENOBUFS'], ['ETIMEDOUT']])('a %s from git status is a failure, not a missing repository', (code) => {
242
+ intercept('status', fail(code));
243
+ expect((0, worktree_1.gitState)(root, true)).toEqual({ kind: 'failed' });
244
+ });
245
+ test('git missing from PATH is a failure', () => {
246
+ intercept('rev-parse', fail('ENOENT'));
247
+ expect((0, worktree_1.gitState)(root, true)).toEqual({ kind: 'failed' });
248
+ });
249
+ test('git status gets a 64 MiB buffer', () => {
250
+ let seen;
251
+ intercept('status', (opts) => { seen = opts.maxBuffer; throw Object.assign(new Error('stop'), { code: 'ENOBUFS' }); });
252
+ (0, worktree_1.gitState)(root, true);
253
+ expect(seen).toBeGreaterThanOrEqual(64 * 1024 * 1024);
254
+ });
255
+ test('the reasons are the stable user-facing strings', () => {
256
+ expect(worktree_1.REASON_NOT_REPO).toBe('not inside a git repository with a commit');
257
+ expect(worktree_1.REASON_GIT_FAILED).toBe('git command failed or timed out');
258
+ });
259
+ });
@@ -7,6 +7,7 @@ const fs_1 = __importDefault(require("fs"));
7
7
  const os_1 = __importDefault(require("os"));
8
8
  const path_1 = __importDefault(require("path"));
9
9
  const write_html_1 = require("../../../src/core/dashboard/write-html");
10
+ const platform_1 = require("../../helpers/platform");
10
11
  describe('writeHtmlAtomically', () => {
11
12
  let root;
12
13
  beforeEach(() => { root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-html-')); });
@@ -85,9 +86,7 @@ describe('writeHtmlAtomically', () => {
85
86
  open.mockRestore();
86
87
  }
87
88
  });
88
- it('uses mode 0600 for a POSIX adjacent temporary file', () => {
89
- if (process.platform === 'win32')
90
- return;
89
+ (0, platform_1.posixOnly)('uses mode 0600 for a POSIX adjacent temporary file', () => {
91
90
  const target = path_1.default.join(root, 'posix.html');
92
91
  const open = jest.spyOn(fs_1.default, 'openSync');
93
92
  try {
@@ -8,6 +8,7 @@ const os_1 = __importDefault(require("os"));
8
8
  const path_1 = __importDefault(require("path"));
9
9
  const evidence_fixtures_1 = require("../../helpers/evidence-fixtures");
10
10
  const store_1 = require("../../../src/core/evidence/store");
11
+ const platform_1 = require("../../helpers/platform");
11
12
  describe('cycle evidence store', () => {
12
13
  let root;
13
14
  beforeEach(() => { root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-evidence-')); });
@@ -20,9 +21,7 @@ describe('cycle evidence store', () => {
20
21
  expect(fs_1.default.readdirSync(dir)).toEqual([`${evidence.cycleId}.json`]);
21
22
  expect(JSON.parse(fs_1.default.readFileSync(path_1.default.join(dir, `${evidence.cycleId}.json`), 'utf8')).qa.fixes).toBe(0);
22
23
  });
23
- test('rejects symlinked evidence ancestors without writing outside root', () => {
24
- if (process.platform === 'win32')
25
- return;
24
+ (0, platform_1.posixOnly)('rejects symlinked evidence ancestors without writing outside root', () => {
26
25
  const outside = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-evidence-outside-'));
27
26
  fs_1.default.symlinkSync(outside, path_1.default.join(root, '.awm'));
28
27
  expect(() => (0, store_1.writeCycleEvidence)(root, (0, evidence_fixtures_1.cycleEvidenceFixture)())).toThrow(/symlink/);
@@ -8,6 +8,7 @@ const os_1 = __importDefault(require("os"));
8
8
  const path_1 = __importDefault(require("path"));
9
9
  const store_1 = require("../../../src/core/ledger/store");
10
10
  const types_1 = require("../../../src/core/ledger/types");
11
+ const platform_1 = require("../../helpers/platform");
11
12
  function mkTmp() {
12
13
  return fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-ledger-'));
13
14
  }
@@ -191,18 +192,11 @@ describe('ledger store — archive', () => {
191
192
  expect((0, store_1.listEntries)(cwd, 'feat-x')).toEqual([expect.objectContaining({ signature: 'active-evidence' })]);
192
193
  expect(fs_1.default.readFileSync(archivePath, 'utf-8')).toContain('archived-evidence');
193
194
  });
194
- test('refuses a dangling symlink archive destination and preserves the active ledger', () => {
195
+ (0, platform_1.symlinkTest)('refuses a dangling symlink archive destination and preserves the active ledger', () => {
195
196
  (0, store_1.addEntry)(cwd, entry({ signature: 'active-evidence' }));
196
197
  const archivePath = path_1.default.join(cwd, '.awm', 'ledger', 'archive', 'feat-x-20260606T000000.jsonl');
197
198
  fs_1.default.mkdirSync(path_1.default.dirname(archivePath), { recursive: true });
198
- try {
199
- fs_1.default.symlinkSync(path_1.default.join(cwd, 'missing-target.jsonl'), archivePath);
200
- }
201
- catch (error) {
202
- if (process.platform === 'win32')
203
- return;
204
- throw error;
205
- }
199
+ fs_1.default.symlinkSync(path_1.default.join(cwd, 'missing-target.jsonl'), archivePath);
206
200
  expect(() => (0, store_1.archiveLedger)(cwd, 'feat-x', '20260606T000000')).toThrow(/archive.*already exists/i);
207
201
  expect(fs_1.default.lstatSync(archivePath).isSymbolicLink()).toBe(true);
208
202
  expect((0, store_1.listEntries)(cwd, 'feat-x')).toEqual([expect.objectContaining({ signature: 'active-evidence' })]);
@@ -66,6 +66,27 @@ describe('discoverProcessModels', () => {
66
66
  expect(r.models.map((m) => m.name)).toEqual(['mi-proceso']);
67
67
  expect(r.diagnostics).toEqual([]);
68
68
  });
69
+ it('description sobrevive parse → discover, tal cual la lee YAML', () => {
70
+ const saved = { HOME: process.env.HOME, AWM_HOME: process.env.AWM_HOME };
71
+ process.env.HOME = path_1.default.join(tmp, 'home');
72
+ process.env.AWM_HOME = path_1.default.join(tmp, 'home', '.awm');
73
+ try {
74
+ const root = registry(path_1.default.join(tmp, 'rd'), {
75
+ 'con-desc': FIXTURE.replace('NAME', 'con-desc').replace('created:', 'description: "Use when: tracing\\u00A0"\ncreated:'),
76
+ });
77
+ const r = (0, discover_1.discoverProcessModels)([root]);
78
+ expect(r.diagnostics).toEqual([]);
79
+ expect(r.models.map((m) => m.description)).toEqual(['Use when: tracing ']);
80
+ }
81
+ finally {
82
+ for (const [k, v] of Object.entries(saved)) {
83
+ if (v === undefined)
84
+ delete process.env[k];
85
+ else
86
+ process.env[k] = v;
87
+ }
88
+ }
89
+ });
69
90
  it('un modelo roto no impide descubrir los sanos', () => {
70
91
  const root = registry(path_1.default.join(tmp, 'r2'), {
71
92
  'sano': FIXTURE.replace('NAME', 'sano'),