kushi-agents 5.4.3 → 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 (29) hide show
  1. package/README.md +1 -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 +7 -4
  7. package/plugin/prompts/bootstrap.prompt.md +2 -1
  8. package/plugin/prompts/consolidate.prompt.md +4 -1
  9. package/plugin/prompts/migrate-files.prompt.md +29 -0
  10. package/plugin/skills/aggregate-project/SKILL.md +3 -3
  11. package/plugin/skills/bootstrap-project/SKILL.md +6 -6
  12. package/plugin/skills/consolidate-evidence/SKILL.md +94 -1
  13. package/plugin/skills/migrate-per-user-files/SKILL.md +47 -0
  14. package/plugin/skills/migrate-per-user-files/evals/evals.json +41 -0
  15. package/plugin/skills/migrate-per-user-files/migrate.ps1 +136 -0
  16. package/plugin/skills/migrate-per-user-files/references/migration-strategy.md +23 -0
  17. package/plugin/skills/pull-ado/SKILL.md +1 -1
  18. package/plugin/skills/pull-crm/SKILL.md +1 -1
  19. package/plugin/skills/pull-email/SKILL.md +1 -1
  20. package/plugin/skills/pull-loop/SKILL.md +1 -1
  21. package/plugin/skills/pull-meetings/SKILL.md +1 -1
  22. package/plugin/skills/pull-misc/SKILL.md +3 -3
  23. package/plugin/skills/pull-onenote/SKILL.md +1 -1
  24. package/plugin/skills/pull-sharepoint/SKILL.md +1 -1
  25. package/plugin/skills/pull-teams/SKILL.md +1 -1
  26. package/plugin/skills/refresh-project/SKILL.md +5 -5
  27. package/plugin/skills/self-check/SKILL.md +1 -0
  28. package/plugin/skills/self-check/run.ps1 +34 -0
  29. package/src/per-user-files.test.mjs +137 -0
@@ -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
@@ -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) per the doctrine's escalation rule.
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.