kushi-agents 5.4.2 → 5.4.4
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 +9 -0
- package/package.json +2 -2
- package/plugin/agents/kushi.agent.md +1 -0
- package/plugin/instructions/bootstrap-status-format.instructions.md +14 -13
- package/plugin/instructions/multi-user-shared-files.instructions.md +117 -87
- package/plugin/plugin.json +32 -9
- package/plugin/prompts/bootstrap.prompt.md +2 -1
- package/plugin/prompts/consolidate.prompt.md +4 -1
- package/plugin/prompts/create-skill.prompt.md +26 -0
- package/plugin/prompts/doctor.prompt.md +31 -0
- package/plugin/prompts/explain.prompt.md +22 -0
- package/plugin/prompts/global.prompt.md +28 -0
- package/plugin/prompts/lint.prompt.md +28 -0
- package/plugin/prompts/migrate-files.prompt.md +29 -0
- package/plugin/prompts/promote.prompt.md +26 -0
- package/plugin/prompts/schema-evolve.prompt.md +27 -0
- package/plugin/prompts/teach.prompt.md +25 -0
- package/plugin/skills/aggregate-project/SKILL.md +3 -3
- package/plugin/skills/bootstrap-project/SKILL.md +6 -6
- package/plugin/skills/consolidate-evidence/SKILL.md +94 -1
- package/plugin/skills/migrate-per-user-files/SKILL.md +47 -0
- package/plugin/skills/migrate-per-user-files/evals/evals.json +41 -0
- package/plugin/skills/migrate-per-user-files/migrate.ps1 +136 -0
- package/plugin/skills/migrate-per-user-files/references/migration-strategy.md +23 -0
- package/plugin/skills/pull-ado/SKILL.md +1 -1
- package/plugin/skills/pull-crm/SKILL.md +1 -1
- package/plugin/skills/pull-email/SKILL.md +1 -1
- package/plugin/skills/pull-loop/SKILL.md +1 -1
- package/plugin/skills/pull-meetings/SKILL.md +1 -1
- package/plugin/skills/pull-misc/SKILL.md +3 -3
- package/plugin/skills/pull-onenote/SKILL.md +1 -1
- package/plugin/skills/pull-sharepoint/SKILL.md +1 -1
- package/plugin/skills/pull-teams/SKILL.md +1 -1
- package/plugin/skills/refresh-project/SKILL.md +5 -5
- package/plugin/skills/self-check/SKILL.md +1 -0
- package/plugin/skills/self-check/run.ps1 +34 -0
- package/src/per-user-files.test.mjs +137 -0
|
@@ -171,7 +171,7 @@ Every refresh writes `Evidence/<alias>/refresh-reports/<YYYY-MM-DD>-<HHMM>-oneno
|
|
|
171
171
|
## References (v4.4.7)
|
|
172
172
|
|
|
173
173
|
- Name → ID resolution follows ..\..\instructions\fuzzy-disambiguation.instructions.md (universal fuzzy contract).
|
|
174
|
-
- After this pull completes, the per-source verification gate runs: ..\..\instructions\per-source-verification-gate.instructions.md (retry once, then write FOLLOW-UPS.md).
|
|
174
|
+
- After this pull completes, the per-source verification gate runs: ..\..\instructions\per-source-verification-gate.instructions.md (retry once, then write Evidence/<alias>/FOLLOW-UPS.md per v5.4.4+ per-user files doctrine; consolidated at _Consolidated/FOLLOW-UPS.md).
|
|
175
175
|
|
|
176
176
|
|
|
177
177
|
## Issue Recovery
|
|
@@ -183,7 +183,7 @@ An entity that cannot meet the threshold is flagged `low_signal: true` in `_inde
|
|
|
183
183
|
## References (v4.4.7)
|
|
184
184
|
|
|
185
185
|
- Name → ID resolution follows ..\..\instructions\fuzzy-disambiguation.instructions.md (universal fuzzy contract).
|
|
186
|
-
- After this pull completes, the per-source verification gate runs: ..\..\instructions\per-source-verification-gate.instructions.md (retry once, then write FOLLOW-UPS.md).
|
|
186
|
+
- After this pull completes, the per-source verification gate runs: ..\..\instructions\per-source-verification-gate.instructions.md (retry once, then write Evidence/<alias>/FOLLOW-UPS.md per v5.4.4+ per-user files doctrine; consolidated at _Consolidated/FOLLOW-UPS.md).
|
|
187
187
|
|
|
188
188
|
|
|
189
189
|
## Issue Recovery
|
|
@@ -170,7 +170,7 @@ After successful pass:
|
|
|
170
170
|
## References (v4.4.7)
|
|
171
171
|
|
|
172
172
|
- Name → ID resolution follows ..\..\instructions\fuzzy-disambiguation.instructions.md (universal fuzzy contract).
|
|
173
|
-
- After this pull completes, the per-source verification gate runs: ..\..\instructions\per-source-verification-gate.instructions.md (retry once, then write FOLLOW-UPS.md).
|
|
173
|
+
- After this pull completes, the per-source verification gate runs: ..\..\instructions\per-source-verification-gate.instructions.md (retry once, then write Evidence/<alias>/FOLLOW-UPS.md per v5.4.4+ per-user files doctrine; consolidated at _Consolidated/FOLLOW-UPS.md).
|
|
174
174
|
|
|
175
175
|
|
|
176
176
|
## Issue Recovery
|
|
@@ -12,7 +12,7 @@ description: "USE WHEN the user says \"refresh <X>\", \"weekly extract for <X>\"
|
|
|
12
12
|
>
|
|
13
13
|
> **Per-user refresh report REQUIRED**: At end of run, write `<project>/Evidence/<alias>/refresh-reports/<YYYY-MM-DD-HHmm>_refresh.md` per `run-reports.instructions.md`. Even on a no-op run. Cite the registry: `Resolved IDs sourced from m365-mutable.json#knownSections.<projectKey>`.
|
|
14
14
|
>
|
|
15
|
-
> **Cleanup on resolution**: When this run resolves a previously-unknown ID/folder/section, prune the stale `no-match` / probe-trail notes from integrations.yml, m365-mutable.json, run-log, and bootstrap-status.md per `cleanup-on-resolution.instructions.md`. UPSERT the newly-resolved ID into `m365-mutable.json#knownSections.<projectKey>` mid-run.
|
|
15
|
+
> **Cleanup on resolution**: When this run resolves a previously-unknown ID/folder/section, prune the stale `no-match` / probe-trail notes from integrations.yml, m365-mutable.json, run-log, and `Evidence/<alias>/bootstrap-status.md` (per-user, v5.4.4+) per `cleanup-on-resolution.instructions.md`. UPSERT the newly-resolved ID into `m365-mutable.json#knownSections.<projectKey>` mid-run.
|
|
16
16
|
>
|
|
17
17
|
> **Capture learnings**: Any quirk / fix / workaround discovered mid-run is appended to `<KUSHI_ROOT>/plugin/learnings/<source>.md` in the same turn per `capture-learnings.instructions.md`.
|
|
18
18
|
>
|
|
@@ -83,7 +83,7 @@ For each marker (chronological order):
|
|
|
83
83
|
2. Re-issue the canonical WorkIQ query (NOT the doubled-strict — give the first prompt another chance first; if it fails, then doubled-strict).
|
|
84
84
|
3. If success → write the artifact to the current week's `Evidence/<alias>/<source>/weekly/<YYYY-MM-DD>_<source>-csc.md` per `weekly-csc.instructions.md` (v4.9.0+); or `{snapshot,stream}/` for pre-v4.9.0 legacy layout. Delete the marker. Append a `drained:` entry to the refresh report's `## Deferred-retry drain` section.
|
|
85
85
|
4. If failure → increment `attempts`, update `last_attempt_at`, leave marker in place. Append a `still-deferred:` entry to the report.
|
|
86
|
-
5. If `attempts >= 5` after this run → also promote to a row in `<project>/OPEN-QUESTIONS-DRAFT.md` (or `State/09_open-questions.md` on `full` profile
|
|
86
|
+
5. If `attempts >= 5` after this run → also promote to a row in `<project>/Evidence/<alias>/OPEN-QUESTIONS-DRAFT.md` (per-user authored, v5.4.4+; cross-contributor rollup at `_Consolidated/OPEN-QUESTIONS-DRAFT.md` is owned by `consolidate-evidence`) — or `State/09_open-questions.md` on `full` profile — per the doctrine's escalation rule.
|
|
87
87
|
|
|
88
88
|
**NEVER call `m365_get_*` / Graph as a fallback during drain.** A drain failure is just another deferral.
|
|
89
89
|
|
|
@@ -129,7 +129,7 @@ if gate.status == "fail":
|
|
|
129
129
|
retry = dispatch(source, --window <same>, --retry)
|
|
130
130
|
gate2 = run_verification_gate(source, retry)
|
|
131
131
|
if gate2.status == "fail":
|
|
132
|
-
append_to_followups(<project>/FOLLOW-UPS.md, gate2) # 5-field shape (D19)
|
|
132
|
+
append_to_followups(<project>/Evidence/<alias>/FOLLOW-UPS.md, gate2) # per-user (v5.4.4+); _Consolidated/FOLLOW-UPS.md emitted by consolidate-evidence; 5-field shape (D19)
|
|
133
133
|
append_to_runlog(<project>/Evidence/run-log.yml,
|
|
134
134
|
source, status: "failed-gate",
|
|
135
135
|
gate_failures: gate2.failures,
|
|
@@ -178,7 +178,7 @@ Display:
|
|
|
178
178
|
- Per-source table: items pulled, weeks affected, errors, **gate status** (pass / retried-pass / failed-followups-written).
|
|
179
179
|
- Side-by-side mutable hints written this run (per `side-by-side-config.instructions.md`).
|
|
180
180
|
- New Open Questions opened/resolved/staled.
|
|
181
|
-
- **Open follow-ups delta** — count of entries newly written to `<project>/FOLLOW-UPS.md` this run + count of entries auto-moved from Open → Resolved by `cleanup-on-resolution.instructions.md`.
|
|
181
|
+
- **Open follow-ups delta** — count of entries newly written to `<project>/Evidence/<alias>/FOLLOW-UPS.md` this run + count of entries auto-moved from Open → Resolved by `cleanup-on-resolution.instructions.md`. Cross-contributor view: `<project>/_Consolidated/FOLLOW-UPS.md` (rebuilt by `consolidate-evidence`).
|
|
182
182
|
|
|
183
183
|
## Watermark semantics
|
|
184
184
|
|
|
@@ -205,7 +205,7 @@ Re-running refresh with the same window is safe:
|
|
|
205
205
|
## References (v4.4.7)
|
|
206
206
|
|
|
207
207
|
- Name → ID resolution (any source) follows `..\..\instructions\fuzzy-disambiguation.instructions.md`.
|
|
208
|
-
- After each per-source pull, run the gate per `..\..\instructions\per-source-verification-gate.instructions.md` (retry once → FOLLOW-UPS.md on failure).
|
|
208
|
+
- After each per-source pull, run the gate per `..\..\instructions\per-source-verification-gate.instructions.md` (retry once → `Evidence/<alias>/FOLLOW-UPS.md` on failure; consolidated at `_Consolidated/FOLLOW-UPS.md`).
|
|
209
209
|
- Parallel dispatch follows `..\..\instructions\parallel-execution.instructions.md` (v5.2.0+).
|
|
210
210
|
- Post-pull hooks fired per source via `..\..\skills\_shared\Invoke-Hooks.ps1` (v5.2.0+).
|
|
211
211
|
|
|
@@ -78,6 +78,7 @@ Checks split into **core** (always run) and **deep** (opt-in).
|
|
|
78
78
|
| D39.otel | OpenTelemetry export (v5.2.0+) | `otel.instructions.md` exists, `Emit-OtelSpan.ps1` helper exists, helper short-circuits when `KUSHI_OTEL_ENDPOINT` is unset. Sub-checks: `D39.otel-doctrine-exists`, `D39.otel-helper-exists`, `D39.otel-noop-when-unset`. |
|
|
79
79
|
| D40.global-wiki | Global wiki + multi-wiki routing (v5.3.0+) | `global-wiki.instructions.md` + `multi-wiki-routing.instructions.md` both exist; `src/global-wiki.mjs` exports the init/promote surface; resolved `$KUSHI_GLOBAL_ROOT`'s `State/` (when initialized) carries `scope: global` frontmatter on all root pages; no `> [!warning] potential-customer-leak` callouts are unresolved. Sub-checks: `D40.global-wiki-doctrine-exists`, `D40.routing-doctrine-exists`, `D40.global-wiki-module-exists`, `D40.promote-module-exists`, `D40.global-wiki-shape`, `D40.no-customer-leak`. |
|
|
80
80
|
| D41.stabilization | v5.4.0 stabilization + first-run polish | `plugin/skills/doctor/SKILL.md` + `doctor.ps1` ship; `docs/quickstart.md` exists and is ≤200 lines; `docs/migration/v4-to-v5.md` exists; `docs/audits/v5.4.0-soak-fixture.md` exists and mentions all 10 soak steps; `.github/workflows/pr-checks.yml` invokes both self-check and skill-checker with `-StrictExit`. Sub-checks: `D41.doctor-exists`, `D41.quickstart-exists`, `D41.migration-notes-exist`, `D41.soak-audit-exists`, `D41.pr-checks-strict`. |
|
|
81
|
+
| D42.per-user-files-scoping | v5.4.4 per-user truth | SKILLs and prompts mentioning `bootstrap-status.md`, `FOLLOW-UPS.md`, or `OPEN-QUESTIONS-DRAFT.md` must also reference `Evidence/<alias>/` or `_Consolidated/` within ±5 lines. See `plugin/instructions/multi-user-shared-files.instructions.md`. |
|
|
81
82
|
| **CSC weekly-layout checks (kushi v4.9.0)** | | gated on `Resolve-EngagementRoots` — no-ops on the kushi repo itself. |
|
|
82
83
|
| D11.csc | CSC entity coverage + depth | every `Evidence/<alias>/<source>/weekly/*-csc.md` has ≥ 1 entity heading; per-source minimum bullet count + populated-section count (meetings 25/6, email 8/4, teams 6/3, onenote 10/4, sharepoint 8/3, crm 12/5, ado 8/4). Coverage-Notes-only blocks (low-signal escape) are exempt. |
|
|
83
84
|
| D12.csc | CSC section order | every entity block's `###` section headings appear in the canonical order: Participants → Topics → Q&A → Who Said What → Decisions → Dates & Numbers → Action Items → Next Steps → Open Questions → Risks → Customer Asks → Artifacts → Coverage Notes. |
|
|
@@ -2164,6 +2164,40 @@ process.stdout.write(JSON.stringify(out));
|
|
|
2164
2164
|
}
|
|
2165
2165
|
}
|
|
2166
2166
|
|
|
2167
|
+
# D42.per-user-files-scoping (v5.4.4+)
|
|
2168
|
+
# Three previously-shared root files are now per-user under Evidence/<alias>/.
|
|
2169
|
+
# Whenever a SKILL.md or prompt file mentions one of the basenames, the same file
|
|
2170
|
+
# MUST also reference 'Evidence/<alias>/' or '_Consolidated/' within a 5-line window
|
|
2171
|
+
# of each occurrence so readers see the per-user scoping in context.
|
|
2172
|
+
$perUserBasenames = @('bootstrap-status.md','FOLLOW-UPS.md','OPEN-QUESTIONS-DRAFT.md')
|
|
2173
|
+
$perUserScopeRe = [regex]'Evidence[\\/][^\s\)`]+[\\/]|_Consolidated[\\/]'
|
|
2174
|
+
$d42Targets = @()
|
|
2175
|
+
foreach ($d in $skillDirs) {
|
|
2176
|
+
$s = Join-Path $d.FullName 'SKILL.md'
|
|
2177
|
+
if (Test-Path $s) { $d42Targets += [pscustomobject]@{ Path=$s; Lines=Get-Content -Path $s } }
|
|
2178
|
+
}
|
|
2179
|
+
foreach ($p in $promptFiles) {
|
|
2180
|
+
$d42Targets += [pscustomobject]@{ Path=$p.FullName; Lines=Get-Content -Path $p.FullName }
|
|
2181
|
+
}
|
|
2182
|
+
foreach ($t in $d42Targets) {
|
|
2183
|
+
$lines = $t.Lines
|
|
2184
|
+
for ($i = 0; $i -lt $lines.Count; $i++) {
|
|
2185
|
+
$line = $lines[$i]
|
|
2186
|
+
foreach ($bn in $perUserBasenames) {
|
|
2187
|
+
if ($line -match [regex]::Escape($bn)) {
|
|
2188
|
+
# Skip when the same line already scopes the mention (per-user OR consolidated path).
|
|
2189
|
+
if ($perUserScopeRe.IsMatch($line)) { continue }
|
|
2190
|
+
# Allow when 'Evidence/<alias>/' / '_Consolidated/' appears within ±5 lines.
|
|
2191
|
+
$lo = [Math]::Max(0, $i - 5)
|
|
2192
|
+
$hi = [Math]::Min($lines.Count - 1, $i + 5)
|
|
2193
|
+
$window = ($lines[$lo..$hi] -join "`n")
|
|
2194
|
+
if ($perUserScopeRe.IsMatch($window)) { continue }
|
|
2195
|
+
Add-Finding 'D42.per-user-files-scoping' 'Stabilization' 'error' "$($t.Path):$($i+1) mentions $bn at project root without per-user (Evidence/<alias>/) or consolidated (_Consolidated/) scoping" "Reword the mention to reference Evidence/<alias>/$bn (per-user truth) or _Consolidated/$bn (auto-rollup) — see plugin/instructions/multi-user-shared-files.instructions.md." $t.Path ($i+1)
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2200
|
+
|
|
2167
2201
|
# === Output ===
|
|
2168
2202
|
if ($Targeted) {
|
|
2169
2203
|
# Filter findings to those whose code, surface, file path, or message contain the substring.
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// v5.4.4: per-user truth + auto-consolidated root view.
|
|
2
|
+
// Tests cover (a) bootstrap-project SKILL.md target path, (b) self-check D42
|
|
3
|
+
// probe (positive + negative), and (c) migrate-per-user-files migration
|
|
4
|
+
// script (fresh migration, idempotent no-op, defensive needs-merge).
|
|
5
|
+
|
|
6
|
+
import { test } from 'node:test';
|
|
7
|
+
import assert from 'node:assert/strict';
|
|
8
|
+
import { spawnSync } from 'node:child_process';
|
|
9
|
+
import fs from 'node:fs';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import url from 'node:url';
|
|
12
|
+
|
|
13
|
+
const here = path.dirname(url.fileURLToPath(import.meta.url));
|
|
14
|
+
const repoRoot = path.resolve(here, '..');
|
|
15
|
+
const migrate = path.join(repoRoot, 'plugin', 'skills', 'migrate-per-user-files', 'migrate.ps1');
|
|
16
|
+
const selfCheck = path.join(repoRoot, 'plugin', 'skills', 'self-check', 'run.ps1');
|
|
17
|
+
|
|
18
|
+
function pwsh(args) {
|
|
19
|
+
return spawnSync('pwsh', ['-NoProfile', '-File', ...args], { encoding: 'utf8' });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function tmpProject(label) {
|
|
23
|
+
const root = path.join(repoRoot, '.testtmp', `per-user-files-${label}-${Date.now()}`);
|
|
24
|
+
fs.mkdirSync(root, { recursive: true });
|
|
25
|
+
fs.mkdirSync(path.join(root, '.kushi', 'config', 'user'), { recursive: true });
|
|
26
|
+
fs.writeFileSync(
|
|
27
|
+
path.join(root, '.kushi', 'config', 'user', 'project-evidence.yml'),
|
|
28
|
+
'alias: ushak\n'
|
|
29
|
+
);
|
|
30
|
+
return root;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
test('bootstrap-project SKILL.md targets per-user bootstrap-status path', () => {
|
|
34
|
+
const skillMd = fs.readFileSync(
|
|
35
|
+
path.join(repoRoot, 'plugin', 'skills', 'bootstrap-project', 'SKILL.md'),
|
|
36
|
+
'utf8'
|
|
37
|
+
);
|
|
38
|
+
assert.ok(
|
|
39
|
+
/Evidence\/<alias>\/bootstrap-status\.md/.test(skillMd),
|
|
40
|
+
'bootstrap-project SKILL.md must reference Evidence/<alias>/bootstrap-status.md'
|
|
41
|
+
);
|
|
42
|
+
// No bare root mention of the three per-user files outside of the v5.4.4
|
|
43
|
+
// migration callout. We enforce by requiring 'Evidence/<alias>/' on every
|
|
44
|
+
// line that mentions bootstrap-status.md.
|
|
45
|
+
const lines = skillMd.split(/\r?\n/);
|
|
46
|
+
for (let i = 0; i < lines.length; i++) {
|
|
47
|
+
if (/bootstrap-status\.md/.test(lines[i])) {
|
|
48
|
+
const window = lines.slice(Math.max(0, i - 5), Math.min(lines.length, i + 6)).join('\n');
|
|
49
|
+
assert.ok(
|
|
50
|
+
/Evidence[\\/][^\s)`]+[\\/]|_Consolidated[\\/]/.test(window),
|
|
51
|
+
`bootstrap-project SKILL.md line ${i + 1} mentions bootstrap-status.md but is not per-user scoped within 5 lines`
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('self-check D42 probe catches a bare root mention in a synthetic skill', () => {
|
|
58
|
+
// Create a temporary skill that violates D42 and run self-check -Targeted D42 on it.
|
|
59
|
+
const stageRoot = path.join(repoRoot, '.testtmp', `d42-probe-${Date.now()}`);
|
|
60
|
+
// Mirror the minimum file shape self-check expects.
|
|
61
|
+
fs.mkdirSync(path.join(stageRoot, 'plugin', 'skills', 'sample-bad'), { recursive: true });
|
|
62
|
+
fs.mkdirSync(path.join(stageRoot, 'plugin', 'instructions'), { recursive: true });
|
|
63
|
+
fs.mkdirSync(path.join(stageRoot, 'plugin', 'prompts'), { recursive: true });
|
|
64
|
+
fs.mkdirSync(path.join(stageRoot, 'plugin', 'agents'), { recursive: true });
|
|
65
|
+
fs.writeFileSync(
|
|
66
|
+
path.join(stageRoot, 'plugin', 'skills', 'sample-bad', 'SKILL.md'),
|
|
67
|
+
`---\nname: "sample-bad"\nversion: "1.0.0"\ndescription: "USE WHEN testing."\n---\n\n# Sample bad\n\nThis skill writes FOLLOW-UPS.md at the project root.\n`
|
|
68
|
+
);
|
|
69
|
+
fs.writeFileSync(
|
|
70
|
+
path.join(stageRoot, 'plugin', 'agents', 'kushi.agent.md'),
|
|
71
|
+
`---\nname: "kushi"\nversion: "5.4.4"\n---\n`
|
|
72
|
+
);
|
|
73
|
+
try {
|
|
74
|
+
const r = pwsh([selfCheck, '-Root', stageRoot, '-Deep', '-Targeted', 'D42', '-Json']);
|
|
75
|
+
const findings = JSON.parse(r.stdout || '[]');
|
|
76
|
+
const d42 = findings.filter((f) => f.code && f.code.startsWith('D42'));
|
|
77
|
+
assert.ok(d42.length >= 1, `expected at least one D42 finding, got ${d42.length}`);
|
|
78
|
+
assert.match(d42[0].message, /FOLLOW-UPS\.md/);
|
|
79
|
+
} finally {
|
|
80
|
+
fs.rmSync(stageRoot, { recursive: true, force: true });
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('migrate-per-user-files: fresh migration moves all three root files', () => {
|
|
85
|
+
const root = tmpProject('fresh');
|
|
86
|
+
try {
|
|
87
|
+
fs.writeFileSync(path.join(root, 'bootstrap-status.md'), 'BS-1\n');
|
|
88
|
+
fs.writeFileSync(path.join(root, 'FOLLOW-UPS.md'), 'FU-1\n');
|
|
89
|
+
fs.writeFileSync(path.join(root, 'OPEN-QUESTIONS-DRAFT.md'), 'OQ-1\n');
|
|
90
|
+
const r = pwsh([migrate, '-ProjectRoot', root, '-Apply']);
|
|
91
|
+
assert.equal(r.status, 0, `migrate exited ${r.status}: ${r.stdout} :: ${r.stderr}`);
|
|
92
|
+
assert.ok(fs.existsSync(path.join(root, 'Evidence', 'ushak', 'bootstrap-status.md')));
|
|
93
|
+
assert.ok(fs.existsSync(path.join(root, 'Evidence', 'ushak', 'FOLLOW-UPS.md')));
|
|
94
|
+
assert.ok(fs.existsSync(path.join(root, 'Evidence', 'ushak', 'OPEN-QUESTIONS-DRAFT.md')));
|
|
95
|
+
assert.ok(!fs.existsSync(path.join(root, 'bootstrap-status.md')));
|
|
96
|
+
assert.ok(!fs.existsSync(path.join(root, 'FOLLOW-UPS.md')));
|
|
97
|
+
assert.ok(!fs.existsSync(path.join(root, 'OPEN-QUESTIONS-DRAFT.md')));
|
|
98
|
+
const runLog = fs.readFileSync(path.join(root, 'Evidence', 'run-log.yml'), 'utf8');
|
|
99
|
+
assert.match(runLog, /migrations:/);
|
|
100
|
+
assert.match(runLog, /alias: ushak/);
|
|
101
|
+
} finally {
|
|
102
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('migrate-per-user-files: re-running --apply on migrated repo is a no-op', () => {
|
|
107
|
+
const root = tmpProject('idem');
|
|
108
|
+
try {
|
|
109
|
+
fs.writeFileSync(path.join(root, 'bootstrap-status.md'), 'X\n');
|
|
110
|
+
fs.writeFileSync(path.join(root, 'FOLLOW-UPS.md'), 'Y\n');
|
|
111
|
+
fs.writeFileSync(path.join(root, 'OPEN-QUESTIONS-DRAFT.md'), 'Z\n');
|
|
112
|
+
const r1 = pwsh([migrate, '-ProjectRoot', root, '-Apply']);
|
|
113
|
+
assert.equal(r1.status, 0);
|
|
114
|
+
const r2 = pwsh([migrate, '-ProjectRoot', root, '-Apply']);
|
|
115
|
+
assert.equal(r2.status, 0);
|
|
116
|
+
assert.match(r2.stdout, /nothing-to-do/);
|
|
117
|
+
} finally {
|
|
118
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test('migrate-per-user-files: refuses to overwrite when per-user copy differs', () => {
|
|
123
|
+
const root = tmpProject('merge');
|
|
124
|
+
try {
|
|
125
|
+
fs.mkdirSync(path.join(root, 'Evidence', 'ushak'), { recursive: true });
|
|
126
|
+
fs.writeFileSync(path.join(root, 'Evidence', 'ushak', 'bootstrap-status.md'), 'A\n');
|
|
127
|
+
fs.writeFileSync(path.join(root, 'bootstrap-status.md'), 'B\n');
|
|
128
|
+
const r = pwsh([migrate, '-ProjectRoot', root, '-Apply']);
|
|
129
|
+
assert.equal(r.status, 1, `expected exit 1 on needs-merge, got ${r.status}`);
|
|
130
|
+
assert.match(r.stdout, /needs-merge/);
|
|
131
|
+
// Per-user file must NOT have been overwritten.
|
|
132
|
+
assert.equal(fs.readFileSync(path.join(root, 'Evidence', 'ushak', 'bootstrap-status.md'), 'utf8'), 'A\n');
|
|
133
|
+
assert.equal(fs.readFileSync(path.join(root, 'bootstrap-status.md'), 'utf8'), 'B\n');
|
|
134
|
+
} finally {
|
|
135
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
136
|
+
}
|
|
137
|
+
});
|