@sabaiway/agent-workflow-kit 1.9.1 → 1.11.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.
@@ -0,0 +1,168 @@
1
+ // Integration acceptance for hide-footprint against a REAL `git` — what injected-git mocks cannot
2
+ // prove: real `git rev-parse --git-path info/exclude` (incl. a linked worktree), real precedence
3
+ // (tracked .gitignore > .git/info/exclude > global core.excludesFile), and the delegated-memory
4
+ // hand-off (fresh + stale). Every test ISOLATES the git environment (per-test HOME + GIT_CONFIG_GLOBAL
5
+ // + GIT_CONFIG_NOSYSTEM) so the host's real ~/.gitignore_global — which already hides /AGENTS.md,
6
+ // /docs/ai/ — cannot make a real check-ignore pass/fail for host-state reasons (Review fold R2 codex#8).
7
+
8
+ import { describe, it, before, beforeEach, afterEach } from 'node:test';
9
+ import assert from 'node:assert/strict';
10
+ import { mkdtempSync, rmSync, writeFileSync, mkdirSync, readFileSync, existsSync } from 'node:fs';
11
+ import { tmpdir } from 'node:os';
12
+ import { join } from 'node:path';
13
+ import { execFileSync } from 'node:child_process';
14
+ import { hideFootprint, excludePath, START_MARKER } from './hide-footprint.mjs';
15
+
16
+ let gitOk = true;
17
+ before(() => {
18
+ try { execFileSync('git', ['--version'], { encoding: 'utf8' }); } catch { gitOk = false; }
19
+ });
20
+
21
+ const made = [];
22
+ const mkdtemp = (tag) => { const d = mkdtempSync(join(tmpdir(), tag)); made.push(d); return d; };
23
+ afterEach(() => { while (made.length) { try { rmSync(made.pop(), { recursive: true, force: true }); } catch { /* best effort */ } } });
24
+
25
+ // An isolated repo: empty global config (no host excludesFile), committer identity in env.
26
+ const setup = () => {
27
+ const home = mkdtemp('aw-home-');
28
+ const dir = mkdtemp('aw-repo-');
29
+ const gcfg = join(home, '.gitconfig');
30
+ writeFileSync(gcfg, '');
31
+ const env = {
32
+ ...process.env, HOME: home, GIT_CONFIG_GLOBAL: gcfg, GIT_CONFIG_NOSYSTEM: '1',
33
+ GIT_AUTHOR_NAME: 'T', GIT_AUTHOR_EMAIL: 't@e', GIT_COMMITTER_NAME: 'T', GIT_COMMITTER_EMAIL: 't@e',
34
+ };
35
+ const git = (args, cwd = dir) => execFileSync('git', args, { cwd, env, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
36
+ git(['init', '-q', '-b', 'main']);
37
+ return { home, dir, gcfg, env, git, deps: { env, home } };
38
+ };
39
+ const readExclude = (dir) => { const p = join(dir, '.git/info/exclude'); return existsSync(p) ? readFileSync(p, 'utf8') : ''; };
40
+ const count = (s, sub) => s.split(sub).length - 1;
41
+ const checkIgnoreSource = (git, dir, probe) => {
42
+ try { return git(['check-ignore', '-v', '--', probe], dir).split('\t')[0]; } catch { return null; }
43
+ };
44
+
45
+ describe('hide-footprint integration (real git)', { skip: !gitOk }, () => {
46
+ let env;
47
+ beforeEach(() => { env = setup(); });
48
+
49
+ it('resolves info/exclude, writes ONE managed block, and real check-ignore reports it', () => {
50
+ const { dir, git, deps } = env;
51
+ writeFileSync(join(dir, 'AGENTS.md'), '# entry\n');
52
+ const ep = excludePath(deps, dir);
53
+ assert.ok(ep.endsWith('.git/info/exclude'));
54
+ const r = hideFootprint({ dir }, deps);
55
+ assert.equal(r.action, 'created');
56
+ const content = readExclude(dir);
57
+ assert.equal(count(content, START_MARKER), 1, 'exactly one managed block');
58
+ assert.match(checkIgnoreSource(git, dir, 'AGENTS.md') ?? '', /info\/exclude/);
59
+ // Idempotent: a second run changes nothing.
60
+ const r2 = hideFootprint({ dir }, deps);
61
+ assert.equal(r2.action, 'noop');
62
+ assert.equal(readExclude(dir), content);
63
+ });
64
+
65
+ it('works inside a linked git worktree', () => {
66
+ const { dir, git, deps, env: e } = env;
67
+ git(['commit', '-q', '--allow-empty', '-m', 'init']);
68
+ const wt = mkdtemp('aw-wt-');
69
+ rmSync(wt, { recursive: true, force: true }); // git worktree add wants a non-existent path
70
+ git(['worktree', 'add', '-q', wt]);
71
+ writeFileSync(join(wt, 'AGENTS.md'), '# entry\n');
72
+ const r = hideFootprint({ dir: wt }, deps);
73
+ assert.equal(r.visibility, 'hidden');
74
+ assert.equal(count(r.wrote.join('\n'), '/AGENTS.md'), 1);
75
+ assert.match(checkIgnoreSource((a, c) => execFileSync('git', a, { cwd: c, env: e, encoding: 'utf8' }), wt, 'AGENTS.md') ?? '', /exclude/);
76
+ });
77
+
78
+ it('precedence: a path in a TRACKED .gitignore is dropped (redundant), reported by .gitignore', () => {
79
+ const { dir, git, deps } = env;
80
+ writeFileSync(join(dir, '.gitignore'), '/docs/ai/\n');
81
+ git(['add', '.gitignore']);
82
+ const r = hideFootprint({ dir }, deps);
83
+ assert.ok(r.dropped.includes('/docs/ai/'), 'covered by tracked .gitignore → dropped');
84
+ assert.ok(!r.wrote.includes('/docs/ai/'));
85
+ assert.match(checkIgnoreSource(git, dir, 'docs/ai/') ?? '', /\.gitignore/);
86
+ });
87
+
88
+ it('precedence: a path in BOTH global excludes and the local block is reported by the LOCAL block', () => {
89
+ const { dir, git, deps } = env;
90
+ const globalExcludes = join(env.home, 'gitignore_global');
91
+ writeFileSync(globalExcludes, '/AGENTS.md\n');
92
+ git(['config', 'core.excludesFile', globalExcludes]);
93
+ writeFileSync(join(dir, 'AGENTS.md'), '# entry\n');
94
+ const r = hideFootprint({ dir }, deps); // default keeps the global block
95
+ assert.ok(r.wrote.includes('/AGENTS.md'));
96
+ assert.match(checkIgnoreSource(git, dir, 'AGENTS.md') ?? '', /info\/exclude/, 'local exclude wins precedence');
97
+ assert.equal(r.global.action, 'kept');
98
+ });
99
+
100
+ it('a machine-global core.excludesFile NAMED .gitignore is not mistaken for a project .gitignore (no spurious STOP)', () => {
101
+ const { dir, git, deps } = env;
102
+ const globalGitignore = join(env.home, '.gitignore'); // basename collides with a project .gitignore
103
+ writeFileSync(globalGitignore, '/AGENTS.md\n');
104
+ git(['config', 'core.excludesFile', globalGitignore]);
105
+ writeFileSync(join(dir, 'AGENTS.md'), '# entry\n');
106
+ const r = hideFootprint({ dir }, deps); // must not STOP probing an outside-repo path
107
+ assert.ok(r.wrote.includes('/AGENTS.md'), 'AGENTS.md hidden project-local, not dropped as a "tracked .gitignore"');
108
+ assert.match(checkIgnoreSource(git, dir, 'AGENTS.md') ?? '', /info\/exclude/);
109
+ });
110
+
111
+ it('delegated-hidden, FRESH memory: bare project-local lines are absorbed into ONE canonical fence', () => {
112
+ const { dir, deps } = env;
113
+ // What a 1.1.0 memory writes project-local (bare, no fence):
114
+ writeFileSync(join(dir, '.git/info/exclude'), '/AGENTS.md\n/CLAUDE.md\n/docs/ai/\n');
115
+ const r = hideFootprint({ dir }, deps);
116
+ const content = readExclude(dir);
117
+ assert.equal(count(content, START_MARKER), 1, 'one fence');
118
+ assert.equal(count(content, '/AGENTS.md\n'), 1, 'no duplicate AGENTS.md rule');
119
+ // the bare lines are now INSIDE the fence, not loose above it.
120
+ assert.ok(content.indexOf('/AGENTS.md') > content.indexOf(START_MARKER));
121
+ assert.ok(r.wrote.includes('/AGENTS.md') && r.wrote.includes('/docs/ai/'));
122
+ });
123
+
124
+ it('delegated-hidden, STALE memory: a memory-old GLOBAL block is migrated away with --remove-global', () => {
125
+ const { dir, git, deps } = env;
126
+ const globalExcludes = join(env.home, 'gitignore_global');
127
+ writeFileSync(globalExcludes, '# agent-workflow-kit hidden mode (machine-local; remove these lines to un-hide)\n/AGENTS.md\n/docs/ai/\n/docs/ai/.memory-version\n');
128
+ git(['config', 'core.excludesFile', globalExcludes]);
129
+ writeFileSync(join(dir, 'AGENTS.md'), '# entry\n');
130
+ const r = hideFootprint({ dir, removeGlobal: true }, deps);
131
+ assert.equal(r.global.action, 'removed');
132
+ assert.ok(!readFileSync(globalExcludes, 'utf8').includes('/AGENTS.md'), 'global block removed');
133
+ assert.match(checkIgnoreSource(git, dir, 'AGENTS.md') ?? '', /info\/exclude/, 'now hidden project-local');
134
+ });
135
+
136
+ describe('visibility inference (D16)', () => {
137
+ it('tracked anchor → VISIBLE → --reconcile writes zero bytes', () => {
138
+ const { dir, git, deps } = env;
139
+ writeFileSync(join(dir, 'AGENTS.md'), '# entry\n');
140
+ git(['add', 'AGENTS.md']);
141
+ const before = readExclude(dir);
142
+ const r = hideFootprint({ dir, reconcile: true }, deps);
143
+ assert.equal(r.visibility, 'visible');
144
+ assert.equal(readExclude(dir), before, 'no bytes written in visible mode');
145
+ });
146
+
147
+ it('untracked + ignored anchor → HIDDEN → --reconcile runs the hide', () => {
148
+ const { dir, git, deps } = env;
149
+ const globalExcludes = join(env.home, 'gitignore_global');
150
+ writeFileSync(globalExcludes, '/AGENTS.md\n');
151
+ git(['config', 'core.excludesFile', globalExcludes]);
152
+ writeFileSync(join(dir, 'AGENTS.md'), '# entry\n');
153
+ const r = hideFootprint({ dir, reconcile: true }, deps);
154
+ assert.equal(r.visibility, 'hidden');
155
+ assert.ok(r.wrote.includes('/AGENTS.md'));
156
+ });
157
+
158
+ it('untracked + not ignored anchor → AMBIGUOUS → --reconcile surfaces it, writes nothing', () => {
159
+ const { dir, deps } = env;
160
+ writeFileSync(join(dir, 'AGENTS.md'), '# entry\n');
161
+ const before = readExclude(dir);
162
+ const r = hideFootprint({ dir, reconcile: true }, deps);
163
+ assert.equal(r.ambiguous, true);
164
+ assert.equal(r.visibility, 'ambiguous');
165
+ assert.equal(readExclude(dir), before, 'ambiguous → no write, agent ASKs');
166
+ });
167
+ });
168
+ });