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.
Files changed (37) hide show
  1. package/README.md +9 -0
  2. package/package.json +2 -2
  3. package/plugin/agents/kushi.agent.md +1 -0
  4. package/plugin/instructions/bootstrap-status-format.instructions.md +14 -13
  5. package/plugin/instructions/multi-user-shared-files.instructions.md +117 -87
  6. package/plugin/plugin.json +32 -9
  7. package/plugin/prompts/bootstrap.prompt.md +2 -1
  8. package/plugin/prompts/consolidate.prompt.md +4 -1
  9. package/plugin/prompts/create-skill.prompt.md +26 -0
  10. package/plugin/prompts/doctor.prompt.md +31 -0
  11. package/plugin/prompts/explain.prompt.md +22 -0
  12. package/plugin/prompts/global.prompt.md +28 -0
  13. package/plugin/prompts/lint.prompt.md +28 -0
  14. package/plugin/prompts/migrate-files.prompt.md +29 -0
  15. package/plugin/prompts/promote.prompt.md +26 -0
  16. package/plugin/prompts/schema-evolve.prompt.md +27 -0
  17. package/plugin/prompts/teach.prompt.md +25 -0
  18. package/plugin/skills/aggregate-project/SKILL.md +3 -3
  19. package/plugin/skills/bootstrap-project/SKILL.md +6 -6
  20. package/plugin/skills/consolidate-evidence/SKILL.md +94 -1
  21. package/plugin/skills/migrate-per-user-files/SKILL.md +47 -0
  22. package/plugin/skills/migrate-per-user-files/evals/evals.json +41 -0
  23. package/plugin/skills/migrate-per-user-files/migrate.ps1 +136 -0
  24. package/plugin/skills/migrate-per-user-files/references/migration-strategy.md +23 -0
  25. package/plugin/skills/pull-ado/SKILL.md +1 -1
  26. package/plugin/skills/pull-crm/SKILL.md +1 -1
  27. package/plugin/skills/pull-email/SKILL.md +1 -1
  28. package/plugin/skills/pull-loop/SKILL.md +1 -1
  29. package/plugin/skills/pull-meetings/SKILL.md +1 -1
  30. package/plugin/skills/pull-misc/SKILL.md +3 -3
  31. package/plugin/skills/pull-onenote/SKILL.md +1 -1
  32. package/plugin/skills/pull-sharepoint/SKILL.md +1 -1
  33. package/plugin/skills/pull-teams/SKILL.md +1 -1
  34. package/plugin/skills/refresh-project/SKILL.md +5 -5
  35. package/plugin/skills/self-check/SKILL.md +1 -0
  36. package/plugin/skills/self-check/run.ps1 +34 -0
  37. package/src/per-user-files.test.mjs +137 -0
@@ -0,0 +1,29 @@
1
+ ---
2
+ mode: agent
3
+ description: "Migrate root-level bootstrap-status.md / FOLLOW-UPS.md / OPEN-QUESTIONS-DRAFT.md to per-user Evidence/<alias>/ paths (kushi v5.4.4+ doctrine)."
4
+ ---
5
+
6
+ # /kushi.migrate-files
7
+
8
+ One-time migration for projects bootstrapped under kushi ≤ v5.4.3.
9
+
10
+ Dispatch the `migrate-per-user-files` skill at `plugin/skills/migrate-per-user-files/` for the current project. Default to **dry-run**. Show the plan and ask the user to re-run with `--apply` to commit. If they explicitly request "apply now" / "do it" / "go" in the same turn, invoke with `-Apply`.
11
+
12
+ ## Resolves
13
+
14
+ - ProjectRoot = the project folder under the current engagement root (resolve via the standard `Get-ProjectRoot` helper).
15
+
16
+ ## Writes
17
+
18
+ - `<project>/Evidence/<alias>/bootstrap-status.md` (moved from root)
19
+ - `<project>/Evidence/<alias>/FOLLOW-UPS.md` (moved from root)
20
+ - `<project>/Evidence/<alias>/OPEN-QUESTIONS-DRAFT.md` (moved from root)
21
+ - `<project>/Evidence/run-log.yml` (appended `migrations:` entry on `--apply`)
22
+
23
+ ## Defensive behavior
24
+
25
+ If both root and per-user copies exist with different content, the skill emits `[needs-merge]` and exits 1 in `--apply` mode — it will NEVER overwrite a per-user file. Hand-merge, then re-run.
26
+
27
+ ## After migration
28
+
29
+ Run `/kushi.consolidate` to generate the `_Consolidated/` rollups for cross-contributor visibility, then `/kushi.status` to verify the bootstrap status reads from the new per-user path.
@@ -0,0 +1,26 @@
1
+ ---
2
+ name: promote
3
+ description: "Copy a project State page into the global wiki with identifier redaction + back-link + dual log."
4
+ argument-hint: "Project + page. Example: 'promote HCA decisions/macc-terms'."
5
+ agent: kushi
6
+ tools: [search, read/readFile, edit, agent]
7
+ ---
8
+
9
+ ## User Input
10
+
11
+ ```text
12
+ ${input:project_and_page:Project + relative State page, e.g. 'HCA decisions/macc-terms'}
13
+ ```
14
+
15
+ # /promote
16
+
17
+ Route to `@Kushi promote <project> <page>`.
18
+
19
+ Copies `<engagement-root>/<project>/State/<page>.md` into `~/.kushi-global/State/<page>.md`:
20
+ - runs the customer-identifier redactor (refuses without `--force` when hits detected)
21
+ - inserts a back-link to the source project page
22
+ - appends entries to both the project run-log and the global wiki dual log
23
+
24
+ Companion verb: `/global ask <q>` retrieves promoted content.
25
+
26
+ Delegates to `promote` skill.
@@ -0,0 +1,27 @@
1
+ ---
2
+ name: schema-evolve
3
+ description: "Migrate kushi.yaml + Evidence/ across schema versions. Idempotent + dry-run by default."
4
+ argument-hint: "Project. Flags: --apply (real run), --to <version>. Example: 'schema-evolve HCA --apply'."
5
+ agent: kushi
6
+ tools: [search, read/readFile, edit, agent]
7
+ ---
8
+
9
+ ## User Input
10
+
11
+ ```text
12
+ ${input:project_and_flags:Project + flags, e.g. 'HCA' (dry-run), 'HCA --apply', 'HCA --to 5.2.0 --apply'}
13
+ ```
14
+
15
+ # /schema-evolve
16
+
17
+ Route to `@Kushi schema-evolve <project>`.
18
+
19
+ Walks `<engagement-root>/<project>/` and applies schema migrations declared under
20
+ `plugin/schemas/migrations/` to bring `kushi.yaml`, run-log, and Evidence/ layout
21
+ to the current (or `--to`-pinned) schema version.
22
+
23
+ - Default: dry-run — prints planned changes, writes nothing.
24
+ - `--apply` — performs the migration; idempotent (safe to re-run).
25
+ - `--to <ver>` — pin a target version (default = current).
26
+
27
+ Delegates to `schema-evolve` skill.
@@ -0,0 +1,25 @@
1
+ ---
2
+ name: teach
3
+ description: "Persist a reusable fact / preference / pattern under .kushi/learnings/."
4
+ argument-hint: "Topic + body. Example: 'teach HCA naming-convention: services are kebab-case'."
5
+ agent: kushi
6
+ tools: [search, read/readFile, edit, agent]
7
+ ---
8
+
9
+ ## User Input
10
+
11
+ ```text
12
+ ${input:topic_and_body:Topic + body, e.g. 'HCA naming-convention: services are kebab-case'}
13
+ ```
14
+
15
+ # /teach
16
+
17
+ Route to `@Kushi teach <topic>`.
18
+
19
+ Writes a single self-contained learning file under `.kushi/learnings/<slug>.md`
20
+ with frontmatter `topic`, `created`, `last_updated`. Idempotent — re-teach updates
21
+ the existing file in place and appends a `history` entry.
22
+
23
+ Companion verb: `/explain <topic>` retrieves the learning without modifying it.
24
+
25
+ Delegates to `teach` skill.
@@ -20,8 +20,8 @@ This skill **never** writes to `State/`. It only writes to `Evidence/`.
20
20
 
21
21
  1. **Resolve project** — fuzzy-match against `m365-mutable.json knownSections` -> `active_projects` -> `<engagement-root>` subfolders.
22
22
  2. **Resolve window** — default = since last `aggregate`/`refresh`/`bootstrap` watermark in `Evidence/run-log.yml`; fallback 7 days if no watermark.
23
- 3. **Pull each enabled source in turn** (`pull-email`, `pull-teams`, `pull-meetings`, `pull-onenote`, `pull-loop`, `pull-sharepoint`, `pull-crm`, `pull-ado`) — each writes its own snapshot/ + stream/ files under `Evidence/<alias>/<source>/`. **After each pull, run the verification gate** per `..\..\instructions\per-source-verification-gate.instructions.md`: on first failure retry once with the same window; on second failure append a 5-field entry to `<project>/FOLLOW-UPS.md`, log `status: failed-gate` to `Evidence/run-log.yml`, and continue to the next source (never abort the whole aggregate).
24
- 4. **Run `consolidate-evidence`** — merges all aliases' streams into `Evidence/_Consolidated/<YYYY-MM-DD>_consolidated.md`. Skip silently if only one alias exists.
23
+ 3. **Pull each enabled source in turn** (`pull-email`, `pull-teams`, `pull-meetings`, `pull-onenote`, `pull-loop`, `pull-sharepoint`, `pull-crm`, `pull-ado`) — each writes its own snapshot/ + stream/ files under `Evidence/<alias>/<source>/`. **After each pull, run the verification gate** per `..\..\instructions\per-source-verification-gate.instructions.md`: on first failure retry once with the same window; on second failure append a 5-field entry to `<project>/Evidence/<alias>/FOLLOW-UPS.md` (per-user, v5.4.4+; cross-contributor view at `_Consolidated/FOLLOW-UPS.md`), log `status: failed-gate` to `Evidence/run-log.yml`, and continue to the next source (never abort the whole aggregate).
24
+ 4. **Run `consolidate-evidence`** — merges all aliases' streams into `Evidence/_Consolidated/<YYYY-MM-DD>_consolidated.md`, AND emits the three per-user-files rollups: `_Consolidated/bootstrap-status.md`, `_Consolidated/FOLLOW-UPS.md`, `_Consolidated/OPEN-QUESTIONS-DRAFT.md` (Step 5 of consolidate-evidence). Skip the per-source weekly consolidation silently if only one alias exists; the per-user-files rollup ALWAYS runs (single-alias rollup is a verbatim copy with an attribution header).
25
25
  5. **Update `Evidence/run-log.yml`** — record this aggregate run, its watermark, the sources pulled, the alias that produced it.
26
26
  6. **DO NOT run `build-state`.** State/ is not the concern of this skill.
27
27
 
@@ -86,7 +86,7 @@ See `docs/reference/evidence-contract.md` for the full schema (filename rules, f
86
86
  ## References (v4.4.7)
87
87
 
88
88
  - Name → ID resolution (any source) follows `..\..\instructions\fuzzy-disambiguation.instructions.md`.
89
- - After each per-source pull, run the gate per `..\..\instructions\per-source-verification-gate.instructions.md` (retry once → FOLLOW-UPS.md on failure).
89
+ - 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`).
90
90
 
91
91
 
92
92
  ## Issue Recovery
@@ -10,7 +10,7 @@ description: "USE WHEN the user says \"bootstrap <X>\", \"set up project evidenc
10
10
  >
11
11
  > **Verbatim-by-default**: every `pull-<source>` whose boundary is satisfied MUST be dispatched. Silent skips are defects. See `verbatim-by-default.instructions.md`.
12
12
  >
13
- > **Per-user bootstrap report REQUIRED**: at end of run, write `<project>/Evidence/<alias>/refresh-reports/<YYYY-MM-DD-HHmm>_bootstrap.md` per `run-reports.instructions.md`. Distinct from `<project>/bootstrap-status.md` (durable state) — this is per-user run narrative.
13
+ > **Per-user bootstrap report REQUIRED**: at end of run, write `<project>/Evidence/<alias>/refresh-reports/<YYYY-MM-DD-HHmm>_bootstrap.md` per `run-reports.instructions.md`. Distinct from `<project>/Evidence/<alias>/bootstrap-status.md` (per-user durable state, v5.4.4+) — this is per-user run narrative.
14
14
  >
15
15
  > **Cleanup on resolution**: when this run resolves an ID/folder/section, prune stale `no-match` / probe-trail notes per `cleanup-on-resolution.instructions.md`.
16
16
  >
@@ -37,9 +37,9 @@ The active profile is read from `kushi-install.json#profile` next to this agent
37
37
 
38
38
  ## Bootstrap-status artifact
39
39
 
40
- After every run (success or coverage-gaps), write `<project>/bootstrap-status.md` per the format contract in `instructions/bootstrap-status-format.instructions.md`. This is the project's fast-orientation artifact — required tables, normalized status vocabulary (`resolved`, `populated`, `unsynced`, `degraded-list-only`, `throttled-tooManyRequests`, `ado-not-complete`, `completed-with-coverage-gaps`), one-line final status. Do NOT inline run history here; that goes in `update-status.md`.
40
+ After every run (success or coverage-gaps), write `<project>/Evidence/<alias>/bootstrap-status.md` per the format contract in `instructions/bootstrap-status-format.instructions.md`. This is the **per-user** fast-orientation artifact for the project (v5.4.4+) — required tables, normalized status vocabulary (`resolved`, `populated`, `unsynced`, `degraded-list-only`, `throttled-tooManyRequests`, `ado-not-complete`, `completed-with-coverage-gaps`), one-line final status. Do NOT inline run history here; that goes in the per-user refresh report. The cross-contributor rollup at `<project>/_Consolidated/bootstrap-status.md` is owned by `consolidate-evidence` Step 5 — bootstrap MUST NOT write to it directly.
41
41
 
42
- > **Multi-contributor safety (kushi v4.4.0+)**: this file is shared via OneDrive. Per `multi-user-shared-files.instructions.md`, every write MUST: (a) absorb sibling conflict copies, (b) preserve other aliases' rows in `## Contributors who have bootstrapped this project`, (c) cite the discovering alias in per-source rows. Same rules apply to `<project>/integrations.yml`, `<project>/OPEN-QUESTIONS-DRAFT.md`, `<project>/Evidence/run-log.yml`, `<project>/Evidence/contributors.yml`.
42
+ > **Per-user authored files (v5.4.4+)**: per `multi-user-shared-files.instructions.md`, three artifacts that used to live at the project root are now per-user: `bootstrap-status.md`, `FOLLOW-UPS.md`, and `OPEN-QUESTIONS-DRAFT.md`. Bootstrap MUST write all three under `<project>/Evidence/<alias>/<file>` (alias from `Get-KushiConfig -Name 'project-evidence'`). `consolidate-evidence` later emits the merged copies under `<project>/_Consolidated/`. Truly shared root files (`integrations.yml`, `Evidence/run-log.yml`, `Evidence/contributors.yml`) still follow the read-modify-write contract in `multi-user-shared-files.instructions.md`.
43
43
 
44
44
  ## Inputs
45
45
 
@@ -134,7 +134,7 @@ Create the project folder structure (per `engagement-root-resolution.instruction
134
134
 
135
135
  The `State/` subtree is created **only on `full` profile**. On `standard`, only `Evidence/` is scaffolded. State files (when scaffolded) come from `templates/state/*.template.md`.
136
136
 
137
- **Pin hook (v4.5.0):** immediately after scaffold, per `onedrive-pin-policy.instructions.md`, extend the OneDrive pin set to cover the new folders — `<project>/integrations.yml`, `<project>/bootstrap-status.md` (after Step 7 writes it), `<project>/Evidence/<alias>/`, `<project>/Evidence/_Consolidated/` (when created), and `<project>/State/` (when scaffolded). Idempotent + additive — never unpins. Skips silently on Linux or when OneDrive isn't running. This keeps every contributor's own slice always-on-device while leaving other contributors' evidence cloud-only.
137
+ **Pin hook (v4.5.0, updated v5.4.4):** immediately after scaffold, per `onedrive-pin-policy.instructions.md`, extend the OneDrive pin set to cover the new folders — `<project>/integrations.yml`, `<project>/Evidence/<alias>/bootstrap-status.md` (after Step 7 writes it), `<project>/Evidence/<alias>/`, `<project>/Evidence/_Consolidated/` (when created), and `<project>/State/` (when scaffolded). Idempotent + additive — never unpins. Skips silently on Linux or when OneDrive isn't running. This keeps every contributor's own slice always-on-device while leaving other contributors' evidence cloud-only.
138
138
 
139
139
  ### Step 3.5 — Customer-hint discovery sweep (REQUIRED, kushi v4.8.0+)
140
140
 
@@ -183,7 +183,7 @@ Per `side-by-side-config.instructions.md` "Verification" rule, list all live con
183
183
  - Side-by-side config table (templates ↔ live files)
184
184
  - Per-source pull table (snapshot count + stream weeks covered + **gate status** [pass / retried-pass / failed-followups-written])
185
185
  - New Open Questions count
186
- - **Open follow-ups count** — if `<project>/FOLLOW-UPS.md` was appended to, surface the count and link to the file
186
+ - **Open follow-ups count** — if `<project>/Evidence/<alias>/FOLLOW-UPS.md` was appended to, surface the count and link to the file (the cross-contributor rollup lives at `<project>/_Consolidated/FOLLOW-UPS.md` after `consolidate-evidence` runs)
187
187
 
188
188
  End with a one-liner pointing at Q&A:
189
189
 
@@ -200,7 +200,7 @@ End with a one-liner pointing at Q&A:
200
200
  ## References (v4.4.7)
201
201
 
202
202
  - Name → ID resolution (any source) follows `..\..\instructions\fuzzy-disambiguation.instructions.md`.
203
- - After each per-source pull, run the gate per `..\..\instructions\per-source-verification-gate.instructions.md` (retry once → FOLLOW-UPS.md on failure).
203
+ - 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 to `_Consolidated/FOLLOW-UPS.md`).
204
204
 
205
205
 
206
206
  ## Issue Recovery
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: "consolidate-evidence"
3
- version: "3.0.0"
3
+ version: "3.1.0"
4
4
  description: "USE WHEN multiple contributors have pulled the same project AND the orchestrator (refresh-project / aggregate-project) needs a merged view at Evidence/_Consolidated/ for downstream skills (build-state, link-entities, ask-project). DO NOT USE manually — it's an internal pass. Capability: merges per-contributor Evidence/<alias>/<source>/ into Evidence/_Consolidated/ for a window using the 3-step reader fallback (_index → weekly → legacy). Latest-fact-wins; cites originating alias."
5
5
  ---
6
6
 
@@ -59,6 +59,98 @@ Snapshots are mostly per-source-of-truth (one canonical entity), so consolidatio
59
59
 
60
60
  Append a `consolidation_runs:` entry: `{ window, contributors, files_written }`.
61
61
 
62
+ ### Step 5 — Consolidate per-user authored files (v5.4.4+)
63
+
64
+ Per `multi-user-shared-files.instructions.md` § "Per-user authored files", three artifacts are written per-user under `Evidence/<alias>/<file>`. This step emits the cross-contributor rollup under `_Consolidated/<file>` for each. Deterministic merge — NO LLM. Idempotent: same input set produces byte-identical output.
65
+
66
+ This step **always runs** (even with a single contributor — a single-alias rollup is just a verbatim copy with an attribution header).
67
+
68
+ For each of the three files (`bootstrap-status.md`, `FOLLOW-UPS.md`, `OPEN-QUESTIONS-DRAFT.md`):
69
+
70
+ 1. Walk `<project>/Evidence/<alias>/<file>` for every alias listed in `Evidence/contributors.yml` (skip aliases that don't have the file).
71
+ 2. Apply the merge rules below.
72
+ 3. Write the result atomically to `<project>/_Consolidated/<file>` (write to `<file>.tmp`, then rename).
73
+
74
+ #### `_Consolidated/bootstrap-status.md`
75
+
76
+ Shape:
77
+
78
+ ```markdown
79
+ # Bootstrap Status (consolidated, <N> contributors)
80
+
81
+ > Auto-generated by `consolidate-evidence` Step 5 at <ISO-ts>. Source of truth lives under each `Evidence/<alias>/bootstrap-status.md`. Do not edit by hand.
82
+
83
+ ## Contributors who have bootstrapped this project
84
+
85
+ | Alias | Display Name | Last Run | Mode | Outcome |
86
+ |---|---|---|---|---|
87
+ | <alias> | ... | ... | ... | ... |
88
+
89
+ ## Latest discovery sweep results (most recent resolved per source, across all contributors)
90
+
91
+ | Source | Status | Discovered by | Discovered at |
92
+ |---|---|---|---|
93
+ | crm | resolved | ushak | 2026-05-27 09:42 EDT |
94
+ | ado | resolved | stand | 2026-05-25 11:01 EDT |
95
+ ...
96
+
97
+ ## Per-contributor bootstrap-status snapshots
98
+
99
+ ### ushak — `Evidence/ushak/bootstrap-status.md`
100
+
101
+ <verbatim copy of that file's body, demoted one heading level>
102
+
103
+ ### stand — `Evidence/stand/bootstrap-status.md`
104
+
105
+ <verbatim copy>
106
+ ```
107
+
108
+ - The "Contributors" table is harvested from each per-user file's `## Run summary` row.
109
+ - The "Latest discovery sweep results" table is harvested from each per-user file's `## Context Artifact Status` table; per source, keep the row whose Status is `resolved` (or `populated`) with the most recent timestamp.
110
+ - Per-contributor snapshot bodies are copied verbatim with heading levels demoted (`#` → `##`, etc.) for unambiguous Markdown nesting.
111
+
112
+ #### `_Consolidated/FOLLOW-UPS.md`
113
+
114
+ Shape:
115
+
116
+ ```markdown
117
+ # Follow-ups (consolidated, <N> contributors)
118
+
119
+ > Auto-generated by `consolidate-evidence` Step 5 at <ISO-ts>. Source of truth lives under each `Evidence/<alias>/FOLLOW-UPS.md`. Do not edit by hand.
120
+
121
+ ## Open follow-ups
122
+
123
+ ### <source> · <YYYY-MM-DD> · <alias> (also reported by: <other-alias>, ...)
124
+
125
+ <verbatim 5-field block from the originating alias's `Evidence/<alias>/FOLLOW-UPS.md`>
126
+
127
+ ## Resolved follow-ups
128
+
129
+ <dedup-merged blocks tagged with originating alias>
130
+ ```
131
+
132
+ - Dedup key per row: `(source, normalized first line of "Next-time-do")`. When two aliases report the same gap, keep the earliest-dated block and append "also reported by: <alias>" to the heading.
133
+ - Resolution status is per-alias — a row stays in "Open" if ANY contributor still has it Open.
134
+
135
+ #### `_Consolidated/OPEN-QUESTIONS-DRAFT.md`
136
+
137
+ Shape:
138
+
139
+ ```markdown
140
+ # Open questions (consolidated, <N> contributors)
141
+
142
+ > Auto-generated by `consolidate-evidence` Step 5 at <ISO-ts>. Source of truth lives under each `Evidence/<alias>/OPEN-QUESTIONS-DRAFT.md`. Do not edit by hand.
143
+
144
+ | # | Question | Source | Earliest asked | Asked by | Status |
145
+ |---|---|---|---|---|---|
146
+ | 1 | ... | ... | 2026-05-20 | ushak, stand | open |
147
+ | 2 | ... | ... | 2026-05-22 | stand | open |
148
+ ```
149
+
150
+ - Dedup key: lowercase + whitespace-collapsed question text.
151
+ - Earliest-asked = MIN(per-alias asked-on date).
152
+ - Asked-by column = sorted union of every alias that asked it.
153
+
62
154
  ## Triggers
63
155
 
64
156
  - "consolidate `<X>` last `<N>` days"
@@ -71,6 +163,7 @@ When this skill exposes a reusable defect (auth pattern, doctrine gap, layout mi
71
163
 
72
164
  ## Changelog
73
165
 
166
+ - **v3.1.0 (kushi v5.4.4, 2026-05-27)**: Step 5 added — consolidate per-user authored files (`bootstrap-status.md`, `FOLLOW-UPS.md`, `OPEN-QUESTIONS-DRAFT.md`) into `_Consolidated/<file>`. Deterministic merge, no LLM. Always runs (even single-contributor). Cross-references per-user truth at `Evidence/<alias>/<file>`.
74
167
  - **v3.0.0 (kushi v4.9.0, 2026-05-26)**: 3-step reader fallback chain (`_index/entities.yml` → `weekly/*.md` → legacy `snapshot/` + `stream/`). New citation form `weekly/<YYYY-MM-DD>_<source>-csc.md#<anchor>`. Legacy citations suffixed `(legacy pre-v4.9.0 layout)`. Output marked with `Source-layout:` footer.
75
168
 
76
169
  ## Validation loop
@@ -0,0 +1,47 @@
1
+ ---
2
+ name: "migrate-per-user-files"
3
+ version: "1.0.0"
4
+ description: "USE WHEN a project was bootstrapped under kushi ≤ v5.4.3 and the three root files (`bootstrap-status.md`, `FOLLOW-UPS.md`, `OPEN-QUESTIONS-DRAFT.md`) need to be relocated to per-user `Evidence/<alias>/` paths AND the user is ready to commit the move. DO NOT USE for routine refreshes (refresh-project already writes to new paths in v5.4.4+) or for shared root files (integrations.yml, kushi.yaml, .settings.yml) which stay at the project root."
5
+ profile: "standard"
6
+ verb: "migrate-files"
7
+ ---
8
+
9
+ # migrate-per-user-files
10
+
11
+ USE WHEN: A project repo was bootstrapped/refreshed under kushi ≤ v5.4.3 and the three previously-shared root files (`bootstrap-status.md`, `FOLLOW-UPS.md`, `OPEN-QUESTIONS-DRAFT.md`) exist at `<project>/`. v5.4.4 promotes those three files to per-user truth at `<project>/Evidence/<alias>/<file>`, with auto-consolidated rollups at `<project>/_Consolidated/<file>` emitted by `consolidate-evidence`. This skill performs the one-time relocation safely.
12
+
13
+ DO NOT USE FOR: routine refreshes (`refresh-project` already writes to the new paths in v5.4.4+) or for the shared files that stay at the root (`integrations.yml`, `kushi.yaml`, `.settings.yml`).
14
+
15
+ ## Steps
16
+
17
+ 1. **Resolve alias** — `Get-KushiConfig -Name 'project-evidence'` returns the current user's alias. Refuse to run if no alias is configured.
18
+ 2. **Discover sources** — For each of the three basenames, check whether `<project>/<file>` exists at the root.
19
+ 3. **Check destination** — For each found source, check whether `<project>/Evidence/<alias>/<file>` already exists.
20
+ - **No destination** → planned action: `move` (root → per-user).
21
+ - **Destination exists with byte-identical content** → planned action: `delete-root-duplicate` (per-user is authoritative, root is stale copy).
22
+ - **Destination exists with different content** → planned action: `needs-merge`. **Defensive: never overwrite.** Emit a `[needs-merge]` row, exit 1 in `--apply` mode (in dry-run, still report and exit 0 so the user sees the full plan).
23
+ 4. **Print the plan** — One row per file: `<basename> | <action> | <reason>`. With `--apply`, also print the destination path that was written.
24
+ 5. **Execute or no-op**:
25
+ - Without `--apply`: dry-run only. Print plan, exit 0.
26
+ - With `--apply`: for each `move` or `delete-root-duplicate`, perform the filesystem operation. Re-running with `--apply` after a successful run is a no-op (nothing to migrate).
27
+ 6. **Log to run-log** — Append a `migrations:` entry to `Evidence/run-log.yml` with timestamp, alias, files moved, files needing merge.
28
+
29
+ ## Arguments
30
+
31
+ - `-ProjectRoot <path>` (required) — Absolute path to the project folder.
32
+ - `-Apply` (switch) — Perform the migration. Without this flag, the script is dry-run only.
33
+ - `-StrictExit` (switch) — Exit 1 on any `needs-merge` row, even in dry-run mode.
34
+
35
+ ## Validation loop
36
+
37
+ After `--apply`, the script self-verifies by re-listing the three root files. If any of them still exists (other than because of a `needs-merge` row), exit 2 with a `[verify]` error.
38
+
39
+ ## Idempotency contract
40
+
41
+ Running with `--apply` on an already-migrated repo MUST be a no-op (exit 0, plan-table prints `nothing-to-do` for all three rows).
42
+
43
+ ## See also
44
+
45
+ - `plugin/instructions/multi-user-shared-files.instructions.md` — the v5.4.4 doctrine.
46
+ - `plugin/skills/consolidate-evidence/SKILL.md` Step 5 — the auto-rollup that consumes per-user files.
47
+ - `references/migration-strategy.md` — why defensive, why content-hash compare, why dry-run by default.
@@ -0,0 +1,41 @@
1
+ {
2
+ "skill": "migrate-per-user-files",
3
+ "version": "1.0.0",
4
+ "description": "Migration script + SKILL.md ship together and document the three target basenames.",
5
+ "cases": [
6
+ {
7
+ "id": "skill-md-exists",
8
+ "name": "SKILL.md and migrate.ps1 are present",
9
+ "input": "verify migrate-per-user-files SKILL.md and migrate.ps1 exist",
10
+ "canary": true,
11
+ "grader_type": "script",
12
+ "expected_assertions": [
13
+ { "type": "file-exists", "path": "plugin/skills/migrate-per-user-files/SKILL.md" },
14
+ { "type": "file-exists", "path": "plugin/skills/migrate-per-user-files/migrate.ps1" }
15
+ ]
16
+ },
17
+ {
18
+ "id": "skill-documents-three-basenames",
19
+ "name": "SKILL.md documents all three target basenames",
20
+ "input": "verify SKILL.md mentions bootstrap-status.md, FOLLOW-UPS.md, and OPEN-QUESTIONS-DRAFT.md",
21
+ "canary": false,
22
+ "grader_type": "script",
23
+ "expected_assertions": [
24
+ { "type": "file-contains", "path": "plugin/skills/migrate-per-user-files/SKILL.md", "needle": "bootstrap-status.md" },
25
+ { "type": "file-contains", "path": "plugin/skills/migrate-per-user-files/SKILL.md", "needle": "FOLLOW-UPS.md" },
26
+ { "type": "file-contains", "path": "plugin/skills/migrate-per-user-files/SKILL.md", "needle": "OPEN-QUESTIONS-DRAFT.md" }
27
+ ]
28
+ },
29
+ {
30
+ "id": "strategy-reference-ships",
31
+ "name": "references/migration-strategy.md documents the defensive contract",
32
+ "input": "verify migration-strategy.md exists and mentions content-hash",
33
+ "canary": false,
34
+ "grader_type": "script",
35
+ "expected_assertions": [
36
+ { "type": "file-exists", "path": "plugin/skills/migrate-per-user-files/references/migration-strategy.md" },
37
+ { "type": "file-contains", "path": "plugin/skills/migrate-per-user-files/references/migration-strategy.md", "needle": "content-hash" }
38
+ ]
39
+ }
40
+ ]
41
+ }
@@ -0,0 +1,136 @@
1
+ #requires -Version 7.0
2
+ <#
3
+ .SYNOPSIS
4
+ Migrate root-level bootstrap-status.md / FOLLOW-UPS.md / OPEN-QUESTIONS-DRAFT.md
5
+ to per-user Evidence/<alias>/ paths (kushi v5.4.4+ doctrine).
6
+
7
+ .PARAMETER ProjectRoot
8
+ Absolute path to the project folder.
9
+
10
+ .PARAMETER Apply
11
+ Perform the migration. Without this switch, the script is dry-run only.
12
+
13
+ .PARAMETER StrictExit
14
+ Exit 1 on any needs-merge row, even in dry-run mode.
15
+ #>
16
+ [CmdletBinding()]
17
+ param(
18
+ [Parameter(Mandatory)] [string] $ProjectRoot,
19
+ [switch] $Apply,
20
+ [switch] $StrictExit
21
+ )
22
+
23
+ $ErrorActionPreference = 'Stop'
24
+ $basenames = @('bootstrap-status.md','FOLLOW-UPS.md','OPEN-QUESTIONS-DRAFT.md')
25
+
26
+ if (-not (Test-Path -LiteralPath $ProjectRoot -PathType Container)) {
27
+ Write-Error "[migrate-per-user-files] ProjectRoot not found: $ProjectRoot"
28
+ exit 2
29
+ }
30
+
31
+ # --- Resolve alias --------------------------------------------------------
32
+ # Read directly from the project's user config — keeps this script standalone
33
+ # and avoids dot-sourcing helpers that have mandatory parameters.
34
+ $alias = $null
35
+ $cfgPath = Join-Path $ProjectRoot '.kushi/config/user/project-evidence.yml'
36
+ if (Test-Path -LiteralPath $cfgPath) {
37
+ $aliasLine = (Get-Content -LiteralPath $cfgPath) | Where-Object { $_ -match '^\s*alias\s*:\s*(\S+)' } | Select-Object -First 1
38
+ if ($aliasLine -and $aliasLine -match '^\s*alias\s*:\s*(\S+)') { $alias = $Matches[1].Trim('"').Trim("'") }
39
+ }
40
+ if (-not $alias) {
41
+ Write-Error "[migrate-per-user-files] No alias configured at $cfgPath. Run setup or set the project-evidence alias before migrating."
42
+ exit 2
43
+ }
44
+
45
+ $perUserDir = Join-Path $ProjectRoot "Evidence\$alias"
46
+ $plan = @()
47
+ $needsMergeCount = 0
48
+ $actedCount = 0
49
+
50
+ foreach ($name in $basenames) {
51
+ $src = Join-Path $ProjectRoot $name
52
+ $dst = Join-Path $perUserDir $name
53
+ $hasSrc = Test-Path -LiteralPath $src -PathType Leaf
54
+ $hasDst = Test-Path -LiteralPath $dst -PathType Leaf
55
+
56
+ if (-not $hasSrc -and -not $hasDst) {
57
+ $plan += [pscustomobject]@{ file=$name; action='nothing-to-do'; reason='neither root nor per-user copy present' }
58
+ continue
59
+ }
60
+ if (-not $hasSrc -and $hasDst) {
61
+ $plan += [pscustomobject]@{ file=$name; action='nothing-to-do'; reason='already migrated' }
62
+ continue
63
+ }
64
+ if ($hasSrc -and -not $hasDst) {
65
+ if ($Apply) {
66
+ if (-not (Test-Path -LiteralPath $perUserDir -PathType Container)) {
67
+ New-Item -ItemType Directory -Path $perUserDir -Force | Out-Null
68
+ }
69
+ Move-Item -LiteralPath $src -Destination $dst
70
+ $actedCount++
71
+ $plan += [pscustomobject]@{ file=$name; action='moved'; reason="-> Evidence/$alias/$name" }
72
+ } else {
73
+ $plan += [pscustomobject]@{ file=$name; action='move'; reason="would move root -> Evidence/$alias/$name" }
74
+ }
75
+ continue
76
+ }
77
+ # Both exist — compare content hashes.
78
+ $srcHash = (Get-FileHash -LiteralPath $src -Algorithm SHA256).Hash
79
+ $dstHash = (Get-FileHash -LiteralPath $dst -Algorithm SHA256).Hash
80
+ if ($srcHash -eq $dstHash) {
81
+ if ($Apply) {
82
+ Remove-Item -LiteralPath $src -Force
83
+ $actedCount++
84
+ $plan += [pscustomobject]@{ file=$name; action='deleted-root-duplicate'; reason='per-user copy is byte-identical' }
85
+ } else {
86
+ $plan += [pscustomobject]@{ file=$name; action='delete-root-duplicate'; reason='would delete byte-identical root copy' }
87
+ }
88
+ } else {
89
+ $needsMergeCount++
90
+ $plan += [pscustomobject]@{ file=$name; action='needs-merge'; reason='root and per-user copies differ — manual merge required, will not overwrite' }
91
+ }
92
+ }
93
+
94
+ Write-Host ""
95
+ Write-Host "[migrate-per-user-files] alias = $alias" -ForegroundColor Cyan
96
+ Write-Host "[migrate-per-user-files] project = $ProjectRoot" -ForegroundColor Cyan
97
+ Write-Host "[migrate-per-user-files] mode = $(if ($Apply) {'APPLY'} else {'DRY-RUN'})" -ForegroundColor Cyan
98
+ Write-Host ""
99
+ $plan | Format-Table -AutoSize | Out-String | Write-Host
100
+
101
+ # --- Run-log append (apply mode only) ------------------------------------
102
+ if ($Apply -and $actedCount -gt 0) {
103
+ $evDir = Join-Path $ProjectRoot 'Evidence'
104
+ if (-not (Test-Path -LiteralPath $evDir -PathType Container)) {
105
+ New-Item -ItemType Directory -Path $evDir -Force | Out-Null
106
+ }
107
+ $log = Join-Path $evDir 'run-log.yml'
108
+ $ts = (Get-Date).ToString('s') + 'Z'
109
+ $moved = ($plan | Where-Object { $_.action -in @('moved','deleted-root-duplicate') } | ForEach-Object { $_.file }) -join ', '
110
+ $merge = ($plan | Where-Object { $_.action -eq 'needs-merge' } | ForEach-Object { $_.file }) -join ', '
111
+ $entry = @(
112
+ "migrations:",
113
+ " - timestamp: $ts",
114
+ " alias: $alias",
115
+ " skill: migrate-per-user-files",
116
+ " files_migrated: [$moved]",
117
+ " files_needs_merge: [$merge]"
118
+ ) -join "`r`n"
119
+ Add-Content -LiteralPath $log -Value $entry
120
+ }
121
+
122
+ # --- Verify (apply mode only) --------------------------------------------
123
+ if ($Apply) {
124
+ foreach ($name in $basenames) {
125
+ $src = Join-Path $ProjectRoot $name
126
+ $stillThere = Test-Path -LiteralPath $src -PathType Leaf
127
+ $row = $plan | Where-Object { $_.file -eq $name } | Select-Object -First 1
128
+ if ($stillThere -and $row.action -notin @('needs-merge','nothing-to-do')) {
129
+ Write-Error "[verify] $name still present at root after migration"
130
+ exit 2
131
+ }
132
+ }
133
+ }
134
+
135
+ if ($needsMergeCount -gt 0 -and ($Apply -or $StrictExit)) { exit 1 }
136
+ exit 0
@@ -0,0 +1,23 @@
1
+ # Migration strategy — per-user file relocation
2
+
3
+ ## Why defensive (refuse to overwrite)
4
+
5
+ The three migrated files are authored artifacts that may have hand edits, accumulated follow-ups, or carefully-curated open-question wording. A naive `Move-Item -Force` could silently destroy hours of work if a per-user copy already exists. We instead detect the collision and refuse — the user must decide which copy wins (or hand-merge them).
6
+
7
+ ## Why content-hash compare
8
+
9
+ If both copies exist with byte-identical content, the per-user copy is authoritative (v5.4.4+) and the root copy is a stale duplicate left behind by some workflow. Deleting it is safe and quiet — no manual merge needed.
10
+
11
+ If the content differs by even one byte, we cannot know which copy is newer, so we emit `[needs-merge]` and stop.
12
+
13
+ ## Why dry-run by default
14
+
15
+ Mass filesystem operations should always print a plan first. The `--apply` flag is the explicit "yes, do it" gate. This matches the pattern used by `apply-ado-update` (preview profile) and the v5.4.3 lessons learned from the profile-allowlist catch-up.
16
+
17
+ ## Why one-time (not part of `refresh-project`)
18
+
19
+ v5.4.4 `refresh-project` already writes to the new per-user paths. The migration is a one-time data move for repos initialized under ≤ v5.4.3. Folding it into refresh would mean checking-and-migrating on every refresh — wasteful and potentially destructive if the user has not yet decided how to handle a `needs-merge` situation.
20
+
21
+ ## Why log to run-log.yml
22
+
23
+ Every project-touching kushi action logs to `Evidence/run-log.yml`. The migration is a project-touching action and follows the same convention so a future `doctor` or `project-status` run can see when the migration happened and which files were affected.
@@ -311,7 +311,7 @@ After successful pass:
311
311
  ## References (v4.4.7)
312
312
 
313
313
  - Name → ID resolution follows ..\..\instructions\fuzzy-disambiguation.instructions.md (universal fuzzy contract).
314
- - After this pull completes, the per-source verification gate runs: ..\..\instructions\per-source-verification-gate.instructions.md (retry once, then write FOLLOW-UPS.md).
314
+ - 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).
315
315
 
316
316
 
317
317
  ## Issue Recovery
@@ -202,7 +202,7 @@ After successful pass:
202
202
  ## References (v4.4.7)
203
203
 
204
204
  - Name → ID resolution follows ..\..\instructions\fuzzy-disambiguation.instructions.md (universal fuzzy contract).
205
- - After this pull completes, the per-source verification gate runs: ..\..\instructions\per-source-verification-gate.instructions.md (retry once, then write FOLLOW-UPS.md).
205
+ - 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).
206
206
 
207
207
 
208
208
  ## Issue Recovery
@@ -193,7 +193,7 @@ An entity that cannot meet the threshold is flagged `low_signal: true` in `_inde
193
193
  ## References (v4.4.7)
194
194
 
195
195
  - Name → ID resolution follows ..\..\instructions\fuzzy-disambiguation.instructions.md (universal fuzzy contract).
196
- - After this pull completes, the per-source verification gate runs: ..\..\instructions\per-source-verification-gate.instructions.md (retry once, then write FOLLOW-UPS.md).
196
+ - 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).
197
197
 
198
198
 
199
199
  ## Issue Recovery
@@ -124,7 +124,7 @@ Per `loop-bootstrap-discovery.instructions.md`:
124
124
  | Pre-flight | Failure action |
125
125
  |---|---|
126
126
  | **A. Workspaces registered** for the resolved project | Refuse to run. Instruct user to run `@Kushi setup --reconfigure`. |
127
- | **B. Pages registered** OR `loop_pages_status: to-be-enumerated` | Run page enumeration via WorkIQ first; on enumerate-fail, write FOLLOW-UPS.md per the gate. |
127
+ | **B. Pages registered** OR `loop_pages_status: to-be-enumerated` | Run page enumeration via WorkIQ first; on enumerate-fail, write Evidence/<alias>/FOLLOW-UPS.md per the gate (v5.4.4+; rollup at _Consolidated/FOLLOW-UPS.md). |
128
128
  | **C. Playwright profile exists** at `~/.kushi/playwright-profile/m365/` or `.../onenote/` | Refuse; instruct: `node plugin/skills/pull-onenote/runner.mjs --bootstrap`. |
129
129
  | **D. URLs are canonical** (matches Loop URL grammar; not synthesized) | Refuse + log to learnings/loop.md per `issue-recovery.instructions.md`. |
130
130
 
@@ -171,7 +171,7 @@ After the pass:
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
@@ -98,7 +98,7 @@ For each project, the bootstrap step:
98
98
  1. **Locates `<project>/external-links.txt`.** If absent, write a starter template (same format as ABN AMRO has today) and mark `boundaries.misc.externalLinksPath` in `integrations.yml`.
99
99
  2. **Parses the file.** Skip comments, skip blank lines, skip placeholder URLs (`<PASTE_*_URL>`, `<TODO*>`).
100
100
  3. **Initializes `misc_links[]`** in `m365-mutable.json#knownSections.<projectKey>` with one entry per non-placeholder link, all `last_status: not-yet-attempted`.
101
- 4. **Records** `boundaries.misc.linkCount` and `placeholderCount` to `bootstrap-status.md`.
101
+ 4. **Records** `boundaries.misc.linkCount` and `placeholderCount` to `Evidence/<alias>/bootstrap-status.md` (per-user, v5.4.4+; consolidated rollup at `_Consolidated/bootstrap-status.md`).
102
102
 
103
103
  ## Step A — enumerate (every refresh)
104
104
 
@@ -117,7 +117,7 @@ The runner branches per `type`:
117
117
 
118
118
  Skip fetch. Write registry entry with `captured_via: delegated`, `delegated_to: pull-loop`. The actual workspace/page capture happens in `pull-loop` (see `plugin/skills/pull-loop/SKILL.md`), which uses the canonical Loop boundary in `<project>/integrations.yml#boundaries.loop.workspace_ids[]` rather than free-text `external-links.txt` URLs.
119
119
 
120
- If the Loop URL pasted into `external-links.txt` references a workspace NOT registered in `boundaries.loop.workspace_ids[]`, `pull-misc` records `last_status: unregistered-loop-workspace` and notes the URL in `<project>/OPEN-QUESTIONS-DRAFT.md` so the user can decide whether to register it via `@Kushi setup --reconfigure`.
120
+ If the Loop URL pasted into `external-links.txt` references a workspace NOT registered in `boundaries.loop.workspace_ids[]`, `pull-misc` records `last_status: unregistered-loop-workspace` and notes the URL in `<project>/Evidence/<alias>/OPEN-QUESTIONS-DRAFT.md` (per-user, v5.4.4+; consolidated rollup at `_Consolidated/OPEN-QUESTIONS-DRAFT.md`) so the user can decide whether to register it via `@Kushi setup --reconfigure`.
121
121
 
122
122
  ### B.2 `web` / `confluence` / `learn` / `docs` / `github` / unknown — HTTP
123
123
 
@@ -263,7 +263,7 @@ Example: `[source: misc/loop/ABN-Core-Team-Sync-2026-03-27 · 2026-05-14]`
263
263
  ## References (v4.4.7)
264
264
 
265
265
  - Name → ID resolution follows ..\..\instructions\fuzzy-disambiguation.instructions.md (universal fuzzy contract).
266
- - After this pull completes, the per-source verification gate runs: ..\..\instructions\per-source-verification-gate.instructions.md (retry once, then write FOLLOW-UPS.md).
266
+ - 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).
267
267
 
268
268
 
269
269
  ## Issue Recovery