kushi-agents 4.3.0 → 4.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/package.json +2 -4
  2. package/plugin/agents/kushi.agent.md +3 -3
  3. package/plugin/instructions/ado-engagement-tree.instructions.md +1 -1
  4. package/plugin/instructions/az-auth-conditional.instructions.md +2 -2
  5. package/plugin/instructions/azure-auth-patterns.instructions.md +8 -8
  6. package/plugin/instructions/bootstrap-status-format.instructions.md +37 -2
  7. package/plugin/instructions/cleanup-on-resolution.instructions.md +1 -1
  8. package/plugin/instructions/crm-bootstrap-discovery.instructions.md +1 -1
  9. package/plugin/instructions/deferred-retry-on-workiq-fail.instructions.md +155 -0
  10. package/plugin/instructions/engagement-root-resolution.instructions.md +16 -13
  11. package/plugin/instructions/evidence-layout-canonical.instructions.md +2 -2
  12. package/plugin/instructions/identity-resolution.instructions.md +19 -12
  13. package/plugin/instructions/m365-id-registry.instructions.md +1 -1
  14. package/plugin/instructions/multi-user-shared-files.instructions.md +87 -0
  15. package/plugin/instructions/run-reports.instructions.md +1 -1
  16. package/plugin/instructions/scope-boundaries.instructions.md +4 -4
  17. package/plugin/instructions/side-by-side-config.instructions.md +23 -17
  18. package/plugin/instructions/tracking.instructions.md +1 -1
  19. package/plugin/instructions/workiq-only.instructions.md +6 -4
  20. package/plugin/lib/Get-KushiConfig.ps1 +214 -0
  21. package/plugin/prompts/bootstrap.prompt.md +64 -35
  22. package/plugin/reference-packs/README.md +1 -1
  23. package/plugin/skills/apply-ado-update/SKILL.md +2 -2
  24. package/plugin/skills/ask-project/SKILL.md +1 -1
  25. package/plugin/skills/bootstrap-project/SKILL.md +18 -16
  26. package/plugin/skills/intro/SKILL.md +2 -2
  27. package/plugin/skills/propose-ado-update/SKILL.md +4 -4
  28. package/plugin/skills/pull-ado/SKILL.md +6 -6
  29. package/plugin/skills/pull-crm/SKILL.md +5 -5
  30. package/plugin/skills/pull-email/SKILL.md +2 -2
  31. package/plugin/skills/pull-meetings/SKILL.md +1 -1
  32. package/plugin/skills/pull-onenote/scripts/recapture-section-url.mjs +2 -1
  33. package/plugin/skills/pull-onenote/write-snapshot.mjs +2 -1
  34. package/plugin/skills/pull-sharepoint/SKILL.md +1 -1
  35. package/plugin/skills/pull-teams/SKILL.md +1 -1
  36. package/plugin/skills/refresh-project/SKILL.md +21 -1
  37. package/plugin/skills/self-check/run.ps1 +24 -1
  38. package/plugin/templates/ado-update/integrations-ado-writes.example.yml +1 -1
  39. package/plugin/templates/init/azuredevops.template.json +159 -0
  40. package/plugin/templates/init/dynamics365.template.json +412 -0
  41. package/plugin/templates/init/m365-auth.template.json +5 -5
  42. package/{.github/config/m365-mutable.json.example → plugin/templates/init/m365-mutable.example.json} +1 -1
  43. package/plugin/templates/init/project-integrations.template.yml +2 -2
  44. package/plugin/templates/init/rsi-program-catalog.template.json +107 -0
  45. package/plugin/templates/snapshot/onenote-page.template.md +3 -3
  46. package/src/config-loader.mjs +156 -0
  47. package/src/constants.mjs +54 -18
  48. package/src/copy-assets.mjs +0 -76
  49. package/src/main.mjs +30 -26
  50. package/src/seed-config.mjs +88 -23
  51. package/src/seed-config.test.mjs +150 -0
  52. package/plugin/templates/init/ado-config.template.yml +0 -21
  53. package/plugin/templates/init/crm-config.template.yml +0 -16
  54. /package/{.github/config/m365-auth.json.example → plugin/templates/init/m365-auth.example.json} +0 -0
@@ -21,13 +21,13 @@ This skill does **NOT** create a new top-level config file. It reads from the co
21
21
 
22
22
  | What | Where | Maintained by |
23
23
  |---|---|---|
24
- | ADO tenant + org + apiVersion + auth strategy | `<engagement-root>/.project-evidence/ado/config.yml` | `bootstrap-project` (global, one-time) |
24
+ | ADO tenant + org + apiVersion + auth strategy | `<workspace>/.kushi/config/shared/integrations.yml` | `bootstrap-project` (global, one-time) |
25
25
  | Per-project Initiative ID (`engagement_id`), area, title filter, discovery hints | `<engagement-root>/<project>/integrations.yml` under `ado:` | `bootstrap-project` + `pull-ado` (auto-discovers and persists) |
26
26
  | Per-project **writes block** (allowlist, autoApply per field, strategy, fieldRefName) | `<engagement-root>/<project>/integrations.yml` under `ado.writes:` | First run of this skill scaffolds the block from the template, then user edits |
27
27
 
28
28
  **Resolution is fully deterministic** — never ask the user for paths the bootstrap layer already resolved:
29
29
 
30
- 1. `<engagement-root>` ← `<workspace>/.kushi/config/project-evidence.yml engagement_root` (per `engagement-root-resolution.instructions.md`).
30
+ 1. `<engagement-root>` ← `<workspace>/.kushi/config/user/project-evidence.yml engagement_root` (per `engagement-root-resolution.instructions.md`).
31
31
  2. `<project>` folder ← fuzzy-match per `engagement-root-resolution.instructions.md` (knownSections → active_projects → subfolders).
32
32
  3. `<project>/integrations.yml ado.engagement_id` — REQUIRED. If `0` or missing → see "Prerequisites" below; never invent an ID.
33
33
  4. `<project>/integrations.yml ado.writes` — if missing, append the block from `<KUSHI_ROOT>/plugin/templates/ado-update/integrations-ado-writes.example.yml` and stop for user confirmation of `fieldRefName`.
@@ -41,7 +41,7 @@ Refuse to produce `proposed.md` and tell the user exactly what's missing if any
41
41
  |---|---|
42
42
  | `<project>/integrations.yml` exists | "Project not bootstrapped. Run `@Kushi bootstrap <project>` first." |
43
43
  | `ado.engagement_id` > 0 | "ADO Initiative not yet linked for `<project>` (engagement_id is 0). pull-ado will auto-discover on next refresh; once `engagement_id` is set in `integrations.yml`, retry." |
44
- | `<engagement-root>/.project-evidence/ado/config.yml` exists with non-placeholder `organization` | "Global ADO config missing or has placeholder org. Run `@Kushi bootstrap <project>` to scaffold, then fill `.project-evidence/ado/config.yml`." |
44
+ | `<workspace>/.kushi/config/shared/integrations.yml` exists with non-placeholder `organization` | "Global ADO config missing or has placeholder org. Run `@Kushi bootstrap <project>` to scaffold, then fill `.kushi/config/shared/integrations.yml`." |
45
45
  | At least one `Evidence/_Consolidated/<date>_consolidated.md` exists | "No consolidated evidence yet for `<project>`. Run `@Kushi refresh <project>` then `consolidate` first." |
46
46
  | Latest consolidated file ≤ 8 days old | Continue but flag `stale-evidence` at top of `proposed.md`. |
47
47
 
@@ -76,7 +76,7 @@ Refuse to produce `proposed.md` and tell the user exactly what's missing if any
76
76
  ## What this skill does NOT do
77
77
 
78
78
  - Does NOT call any ADO `PATCH` or `POST` endpoint.
79
- - Does NOT create or modify the global `.project-evidence/ado/config.yml`.
79
+ - Does NOT create or modify the global `.kushi/config/shared/integrations.yml`.
80
80
  - Does NOT touch `<project>/integrations.yml` other than appending the `ado.writes:` sub-block on first run (preserving every existing key).
81
81
  - Does NOT re-pull evidence (uses the most recent `_Consolidated/` file as-is).
82
82
  - Does NOT write to the ledger (the ledger is written only by `apply-ado-update`).
@@ -27,9 +27,9 @@ WorkIQ-first per `workiq-first.instructions.md` — but **for ADO, REST is prefe
27
27
  - `<project>` — already-resolved project name.
28
28
  - `<alias>` — current contributor.
29
29
  - `<window>` — date range. For snapshot: ignored (always full re-fetch). For stream: `(from, to)`.
30
- - (read) `<engagement-root>/.project-evidence/ado/config.yml` — connection.
30
+ - (read) `<workspace>/.kushi/config/shared/integrations.yml` — connection.
31
31
  - (read) `<engagement-root>/<project>/integrations.yml#ado` — per-project pinned IDs.
32
- - (read) `<engagement-root>/.project-evidence/m365/m365-mutable.json m365Mutable.knownSections.<project>` — pinned hints.
32
+ - (read) `<workspace>/.kushi/config/user/m365-mutable.json m365Mutable.knownSections.<project>` — pinned hints.
33
33
  - (read) `<engagement-root>/<project>/Evidence/<alias>/.settings.yml` — per-(project x user) overrides.
34
34
 
35
35
  ## Resolution hints (pinned in mutable)
@@ -42,15 +42,15 @@ WorkIQ-first per `workiq-first.instructions.md` — but **for ADO, REST is prefe
42
42
 
43
43
  This skill is **HARD-fail** without both:
44
44
 
45
- 1. Global config: `<engagement-root>/.project-evidence/ado/config.yml` exists with non-placeholder `organization` AND `defaultProject`.
45
+ 1. Global config: `<workspace>/.kushi/config/shared/integrations.yml` exists with non-placeholder `organization` AND `defaultProject`.
46
46
  2. Per-project boundary: `<engagement-root>/<project>/integrations.yml#boundaries.ado.area_paths` is non-empty (OR `boundaries.ado.work_item_ids` is pinned).
47
47
 
48
48
  If either is missing, refuse with the exact message:
49
49
 
50
50
  ```
51
51
  ado-config-missing — drop a filled config.yml at
52
- <engagement-root>/.project-evidence/ado/config.yml. See template at
53
- plugin/templates/init/ado-config.template.yml. Then add boundaries.ado.area_paths
52
+ <workspace>/.kushi/config/shared/integrations.yml. See template at
53
+ plugin/templates/init/integrations.template.yml (ado: block). Then add boundaries.ado.area_paths
54
54
  to <project>/integrations.yml.
55
55
  ```
56
56
 
@@ -61,7 +61,7 @@ tenant-wide title-fuzzy scan in v3.7.0+.
61
61
  ## Auth (deterministic, no improvisation)
62
62
 
63
63
  ```powershell
64
- $cfg = Get-Content "<engagement-root>/.project-evidence/ado/config.yml" | ConvertFrom-Yaml
64
+ $cfg = Get-Content "<workspace>/.kushi/config/shared/integrations.yml" | ConvertFrom-Yaml
65
65
  $tok = az account get-access-token `
66
66
  --resource $cfg.azDevOpsResourceId `
67
67
  --tenant $cfg.tenantId `
@@ -27,7 +27,7 @@ WorkIQ-first per `workiq-first.instructions.md`. Thoroughness per `evidence-thor
27
27
  - `<project>` — already-resolved project name.
28
28
  - `<alias>` — current contributor.
29
29
  - `<window>` — date range. For snapshot: ignored (always full re-fetch). For stream: `(from, to)`.
30
- - (read) `<engagement-root>/.project-evidence/m365/m365-mutable.json m365Mutable.knownSections.<project>` — pinned hints.
30
+ - (read) `<workspace>/.kushi/config/user/m365-mutable.json m365Mutable.knownSections.<project>` — pinned hints.
31
31
  - (read) `<engagement-root>/<project>/Evidence/<alias>/.settings.yml` — per-(project x user) overrides.
32
32
 
33
33
  ## Resolution hints (pinned in mutable)
@@ -40,15 +40,15 @@ WorkIQ-first per `workiq-first.instructions.md`. Thoroughness per `evidence-thor
40
40
 
41
41
  This skill is **HARD-fail** without both:
42
42
 
43
- 1. Global config: `<engagement-root>/.project-evidence/crm/config.yml` exists with non-placeholder `environmentUrl`.
43
+ 1. Global config: `<workspace>/.kushi/config/shared/integrations.yml` exists with non-placeholder `environmentUrl`.
44
44
  2. Per-project boundary: `<engagement-root>/<project>/integrations.yml#boundaries.crm.record_ids` OR `boundaries.crm.request_ids` is non-empty.
45
45
 
46
46
  If either is missing, refuse with the exact message:
47
47
 
48
48
  ```
49
49
  crm-config-missing — drop a filled config.yml at
50
- <engagement-root>/.project-evidence/crm/config.yml. See template at
51
- plugin/templates/init/crm-config.template.yml. Then add boundaries.crm.record_ids
50
+ <workspace>/.kushi/config/shared/integrations.yml. See template at
51
+ plugin/templates/init/integrations.template.yml (crm: block). Then add boundaries.crm.record_ids
52
52
  (or request_ids) to <project>/integrations.yml.
53
53
  ```
54
54
 
@@ -195,7 +195,7 @@ If a week file already exists, MERGE (dedupe by event ID, append new events, kee
195
195
 
196
196
  ## Tools (in order)
197
197
 
198
- 1. **Dataverse REST Web API** (preferred for snapshot — gives explicit `$select` + `$expand` control) via `az account get-access-token` against the configured tenant. Read connection from `<engagement-root>/.project-evidence/crm/config.yml`.
198
+ 1. **Dataverse REST Web API** (preferred for snapshot — gives explicit `$select` + `$expand` control) via `az account get-access-token` against the configured tenant. Read connection from `<workspace>/.kushi/config/shared/integrations.yml`.
199
199
  2. **WorkIQ** — only when REST is unavailable; phrase queries to demand verbatim long-text + every annotation (see Step B template above). WorkIQ summarizes by default, so use this path only as fallback.
200
200
  3. **Graph REST** — last resort, soft-fail per `az-auth-conditional.instructions.md`.
201
201
  4. **Ask user** — paste verbatim source content if all above fail.
@@ -26,7 +26,7 @@ Thoroughness per `evidence-thoroughness.instructions.md`; runtime detector + aut
26
26
  - `<project>` — already-resolved project name.
27
27
  - `<alias>` — current contributor.
28
28
  - `<window>` — date range. For snapshot: ignored (always full re-fetch). For stream: `(from, to)`.
29
- - (read) `<engagement-root>/.project-evidence/m365/m365-mutable.json m365Mutable.knownSections.<project>` — pinned hints.
29
+ - (read) `<workspace>/.kushi/config/user/m365-mutable.json m365Mutable.knownSections.<project>` — pinned hints.
30
30
  - (read) `<engagement-root>/<project>/Evidence/<alias>/.settings.yml` — per-(project x user) overrides.
31
31
 
32
32
  ## Resolution hints (pinned in mutable)
@@ -43,7 +43,7 @@ This skill REFUSES to query unless `<engagement-root>/<project>/integrations.yml
43
43
  - `boundaries.email.subject_keywords` — optional narrowing.
44
44
  - `boundaries.date_window_days` — defaults to 30 if absent.
45
45
 
46
- Every WorkIQ ask, every `m365_search_emails` / `m365_list_emails` call, every Graph fallback MUST be scoped to those mailboxes + (if set) sender_domains + subject_keywords. Empty hits inside the boundary → write Coverage Notes citing the limiting key; do NOT widen the scope.
46
+ Every WorkIQ ask MUST be scoped to those mailboxes + (if set) sender_domains + subject_keywords. Empty hits inside the boundary → write Coverage Notes citing the limiting key; do NOT widen the scope. (`m365_search_emails` / `m365_list_emails` / any Graph call is FORBIDDEN per `workiq-only.instructions.md`; on WorkIQ failure, write a deferred-retry marker per `deferred-retry-on-workiq-fail.instructions.md` and continue.)
47
47
 
48
48
  Refusal message when boundary is missing:
49
49
 
@@ -30,7 +30,7 @@ Auth + retry + error logging per `auth-and-retry.instructions.md`. WorkIQ-only p
30
30
  - `<project>` — already-resolved project name.
31
31
  - `<alias>` — current contributor.
32
32
  - `<window>` — date range. For snapshot: ignored (always full re-fetch). For stream: `(from, to)`.
33
- - (read) `<engagement-root>/.project-evidence/m365/m365-mutable.json m365Mutable.knownSections.<project>` — pinned hints (`calendarContext.subjectKeywords`, `calendarContext.knownSeries`).
33
+ - (read) `<workspace>/.kushi/config/user/m365-mutable.json m365Mutable.knownSections.<project>` — pinned hints (`calendarContext.subjectKeywords`, `calendarContext.knownSeries`).
34
34
 
35
35
  ## Discovery (snapshot pass — WorkIQ ONLY per `workiq-only.instructions.md`)
36
36
 
@@ -49,7 +49,8 @@ if (!PROJECT || !ENGAGEMENT_ROOT) {
49
49
  process.exit(2);
50
50
  }
51
51
 
52
- const REG_PATH = path.join(ENGAGEMENT_ROOT, '.project-evidence', 'm365', 'm365-mutable.json');
52
+ const WORKSPACE = args.workspace || process.cwd();
53
+ const REG_PATH = path.join(WORKSPACE, '.kushi', 'config', 'user', 'm365-mutable.json');
53
54
  if (!fs.existsSync(REG_PATH)) {
54
55
  console.error(`[recapture-section-url] Registry not found: ${REG_PATH}`);
55
56
  process.exit(3);
@@ -54,7 +54,8 @@ const TIMEOUT_MS = args.timeout || '120000';
54
54
  const PROJECT = args.project;
55
55
  const ROOT = args['engagement-root'];
56
56
  const ALIAS = args.alias || 'ushak';
57
- const MUTABLE_PATH = args.mutable || path.join(ROOT || '.', '.project-evidence', 'm365', 'm365-mutable.json');
57
+ const WORKSPACE = args.workspace || process.cwd();
58
+ const MUTABLE_PATH = args.mutable || path.join(WORKSPACE, '.kushi', 'config', 'user', 'm365-mutable.json');
58
59
 
59
60
  if ((!JSON_PATH && !SECTION_URL) || !PROJECT || !ROOT) {
60
61
  console.error('Usage: (--json <runner-output> | --section-url <url>) --project <name> --engagement-root <root> [--alias ushak] [--mutable <path>] [--timeout 120000]');
@@ -28,7 +28,7 @@ Thoroughness per `evidence-thoroughness.instructions.md`; runtime detector + aut
28
28
  - `<project>` — already-resolved project name.
29
29
  - `<alias>` — current contributor.
30
30
  - `<window>` — date range. For snapshot: ignored (always full re-fetch). For stream: `(from, to)`.
31
- - (read) `<engagement-root>/.project-evidence/m365/m365-mutable.json m365Mutable.knownSections.<project>` — pinned hints.
31
+ - (read) `<workspace>/.kushi/config/user/m365-mutable.json m365Mutable.knownSections.<project>` — pinned hints.
32
32
  - (read) `<engagement-root>/<project>/Evidence/<alias>/.settings.yml` — per-(project x user) overrides.
33
33
 
34
34
  ## Resolution hints (pinned in mutable)
@@ -28,7 +28,7 @@ Thoroughness per `evidence-thoroughness.instructions.md`; runtime detector + aut
28
28
  - `<project>` — already-resolved project name.
29
29
  - `<alias>` — current contributor.
30
30
  - `<window>` — date range. For snapshot: ignored (always full re-fetch). For stream: `(from, to)`.
31
- - (read) `<engagement-root>/.project-evidence/m365/m365-mutable.json m365Mutable.knownSections.<project>` — pinned hints.
31
+ - (read) `<workspace>/.kushi/config/user/m365-mutable.json m365Mutable.knownSections.<project>` — pinned hints.
32
32
  - (read) `<engagement-root>/<project>/Evidence/<alias>/.settings.yml` — per-(project x user) overrides.
33
33
 
34
34
  ## Resolution hints (pinned in mutable)
@@ -8,7 +8,7 @@ description: "Incremental refresh for an already-bootstrapped project. Reads run
8
8
 
9
9
  > **Verbatim-by-default**: This orchestrator MUST dispatch every enabled `pull-<source>` skill whose boundary in `integrations.yml#boundaries.<source>` is satisfied. Skipping a source silently is a defect. See `verbatim-by-default.instructions.md`.
10
10
  >
11
- > **Discover once, consume deterministically**: Refresh MUST read canonical M365 IDs from `<engagement-root>/.project-evidence/m365/m365-mutable.json#knownSections.<projectKey>` and pass them verbatim into each `pull-*`'s index-extractor query. **Refresh never re-discovers.** If the project entry is missing or a per-source key is empty, re-dispatch through `bootstrap-project` for that source's discovery only, then resume. See `m365-id-registry.instructions.md`.
11
+ > **Discover once, consume deterministically**: Refresh MUST read canonical M365 IDs from `<workspace>/.kushi/config/user/m365-mutable.json#knownSections.<projectKey>` and pass them verbatim into each `pull-*`'s index-extractor query. **Refresh never re-discovers.** If the project entry is missing or a per-source key is empty, re-dispatch through `bootstrap-project` for that source's discovery only, then resume. See `m365-id-registry.instructions.md`.
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
  >
@@ -50,6 +50,26 @@ Profile is read from `kushi-install.json#profile` next to the agent file. Defaul
50
50
  - Else if `sources.<src>.watermark` exists → use `(watermark, today)`.
51
51
  - Else (first refresh after bootstrap, or new source) → fallback to `last 7 days`.
52
52
 
53
+ ### Step 2a — Drain deferred-retry queue (REQUIRED, kushi v4.4.1+, per `deferred-retry-on-workiq-fail.instructions.md`)
54
+
55
+ **Before per-source dispatch**, drain any markers that previous runs deferred. The queue lives at:
56
+
57
+ ```
58
+ <engagement-root>/<project>/Evidence/<alias>/_deferred-retries/*.yml
59
+ ```
60
+
61
+ For each marker (chronological order):
62
+
63
+ 1. Load the YAML marker. Parse `source`, `target`, `window`, `workiq.command`.
64
+ 2. Re-issue the canonical WorkIQ query (NOT the doubled-strict — give the first prompt another chance first; if it fails, then doubled-strict).
65
+ 3. If success → write the artifact to its canonical `Evidence/<alias>/<source>/{snapshot,stream}/` path per the source's normal write rules. Delete the marker. Append a `drained:` entry to the refresh report's `## Deferred-retry drain` section.
66
+ 4. If failure → increment `attempts`, update `last_attempt_at`, leave marker in place. Append a `still-deferred:` entry to the report.
67
+ 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.
68
+
69
+ **NEVER call `m365_get_*` / Graph as a fallback during drain.** A drain failure is just another deferral.
70
+
71
+ After drain, proceed to Step 2 even if some markers remain — the orchestrator never blocks on deferred-retry failures.
72
+
53
73
  ### Step 2 — Per-source dispatch
54
74
 
55
75
  For each enabled source (or just the requested one), call its `pull-<source>` skill with the effective window.
@@ -616,7 +616,7 @@ if ($Deep) {
616
616
  if (-not (Test-Path $layoutInst)) {
617
617
  Add-Finding D14 'Evidence layout' 'warning' "plugin/instructions/evidence-layout-canonical.instructions.md is missing" "Restore the canonical-layout doctrine from kushi v3.12.1." $layoutInst 0
618
618
  }
619
- $allowedSiblings = @('Evidence','State','Reports','.kushi','.kushi-reference','.project-evidence','.vscode','.git')
619
+ $allowedSiblings = @('Evidence','State','Reports','.kushi','.kushi-reference','.vscode','.git')
620
620
  $sourceTokens = @(
621
621
  'email-context','email-summary','email-summaries',
622
622
  'teams-context','teams-summary','teams-summaries',
@@ -655,6 +655,29 @@ if ($Deep) {
655
655
  }
656
656
  }
657
657
  }
658
+
659
+ # D15: legacy path regression guard — no `.project-evidence/{m365,crm,ado}` refs outside CHANGELOG/learnings.
660
+ # v4.4.0+ moved per-user config to `<workspace>/.kushi/config/{user,shared}/`. Any plugin/docs file
661
+ # still referencing the legacy `<engagement-root>/.project-evidence/(m365|crm|ado)` path will mislead
662
+ # skills and authors. CHANGELOG and learnings/ legitimately mention the legacy path for history.
663
+ $legacyPattern = '\.project-evidence[\\/](?:m365|crm|ado)'
664
+ $legacyTargets = @(
665
+ (Join-Path $Root 'plugin'),
666
+ (Join-Path $Root 'docs')
667
+ )
668
+ foreach ($target in $legacyTargets) {
669
+ if (-not (Test-Path $target)) { continue }
670
+ $legacyHits = Get-ChildItem -Path $target -Recurse -File -Include '*.md','*.ps1','*.mjs','*.json','*.yml','*.yaml' -ErrorAction SilentlyContinue |
671
+ Where-Object { $_.FullName -notmatch '[\\/]learnings[\\/]' -and $_.Name -ne 'CHANGELOG.md' } |
672
+ Select-String -Pattern $legacyPattern -ErrorAction SilentlyContinue
673
+ foreach ($m in $legacyHits) {
674
+ # Allow lines that explicitly mark as historical breadcrumb (contain "legacy" or "v4.4.0+" or "was")
675
+ if ($m.Line -match '(?i)\blegacy\b|v4\.4\.0\+|\bwas\b|\breplaces?\b|\bdeprecated\b|\bhistorical\b') { continue }
676
+ $msg = 'Legacy .project-evidence/{m365|crm|ado} reference — v4.4.0+ moved per-user config to <workspace>/.kushi/config/{user,shared}/'
677
+ $fix = "Replace with the new path; if intentionally documenting history, add the word 'legacy' or 'v4.4.0+ replaces' on the same line."
678
+ Add-Finding D15 'Legacy paths' 'warning' $msg $fix $m.Path $m.LineNumber
679
+ }
680
+ }
658
681
  }
659
682
 
660
683
  # === Output ===
@@ -4,7 +4,7 @@
4
4
  # read from the SAME integrations.yml that pull-ado already uses — no separate config file.
5
5
  #
6
6
  # Global ADO connection (tenantId, organization, apiVersion, auth) lives at:
7
- # <engagement-root>/.project-evidence/ado/config.yml
7
+ # <workspace>/.kushi/config/shared/integrations.yml
8
8
  # Do NOT duplicate those keys here.
9
9
 
10
10
  ado:
@@ -0,0 +1,159 @@
1
+ {
2
+ "azureDevOps": {
3
+ "organizationUrl": "https://dev.azure.com/IndustrySolutions",
4
+ "project": "IS Engagements",
5
+ "apiVersion": "7.1",
6
+ "resource": "499b84ac-1321-427f-aa17-267ca6975798",
7
+ "tenantId": "72f988bf-86f1-41af-91ab-2d7cd011db47",
8
+ "allowedTenantIds": [
9
+ "72f988bf-86f1-41af-91ab-2d7cd011db47"
10
+ ],
11
+ "workItemTypes": {
12
+ "engagement": "Engagement",
13
+ "activity": "Activity"
14
+ },
15
+ "queryTemplates": {
16
+ "engagementByExactTitle": "SELECT [System.Id], [System.Title], [System.State], [System.ChangedDate] FROM WorkItems WHERE [System.TeamProject] = '@project' AND [System.WorkItemType] = '@engagementType' AND [System.Title] = '@title' ORDER BY [System.ChangedDate] DESC",
17
+ "engagementByPartialTitle": "SELECT [System.Id], [System.Title], [System.State], [System.ChangedDate] FROM WorkItems WHERE [System.TeamProject] = '@project' AND [System.WorkItemType] = '@engagementType' AND [System.Title] CONTAINS '@titlePart' ORDER BY [System.ChangedDate] DESC",
18
+ "activityByParentEngagementId": "SELECT [System.Id], [System.Title], [System.State], [System.ChangedDate] FROM WorkItems WHERE [System.TeamProject] = '@project' AND [System.WorkItemType] = '@activityType' AND [System.Parent] = @engagementId ORDER BY [System.ChangedDate] DESC"
19
+ },
20
+ "fieldMappings": {
21
+ "common": {
22
+ "id": "System.Id",
23
+ "title": "System.Title",
24
+ "state": "System.State",
25
+ "assignedTo": "System.AssignedTo",
26
+ "tags": "System.Tags",
27
+ "changedDate": "System.ChangedDate",
28
+ "parent": "System.Parent",
29
+ "historyWrite": "System.History"
30
+ },
31
+ "activity": {
32
+ "activityType": "Custom.activity_type",
33
+ "goal": "Custom.activity_goal",
34
+ "successCriteria": "Custom.activity_successcriteria",
35
+ "startDate": "Custom.activity_startdate",
36
+ "endDate": "Custom.activity_enddate",
37
+ "rrStatus": "Custom.activity_rr_status",
38
+ "rr1Type": "Custom.activity_rr1_type",
39
+ "assignedResource1": "Custom.AssignedResource1",
40
+ "rrStartDate1": "Custom.RRStartDate1",
41
+ "rrEndDate1": "Custom.RREndDate1",
42
+ "rr2Type": "Custom.activity_rr2_type",
43
+ "assignedResource2": "Custom.AssignedResource2",
44
+ "rrStartDate2": "Custom.RRStartDate2",
45
+ "rrEndDate2": "Custom.RREndDate2",
46
+ "country": "Custom.Country"
47
+ },
48
+ "engagement": {
49
+ "_comment_status": "Confirm exact engagement status field reference name",
50
+ "status": "System.State",
51
+ "_comment_customer": "Confirm exact customer/account field reference name",
52
+ "customer": "",
53
+ "_comment_primaryLead": "Confirm exact engagement owner/lead field reference name",
54
+ "primaryLead": "System.AssignedTo",
55
+ "_comment_joinKeyIsCrmOpportunityGuid": "Confirm this field exists in this process template",
56
+ "isCrmId": "Custom.ISCRMID",
57
+ "_comment_msxOpportunityId": "Optional if available in your process template",
58
+ "msxOpportunityId": "Custom.MSXOpportunityID"
59
+ },
60
+ "discussion": {
61
+ "_comment_historyRead": "History is available as revisions; confirm whether dedicated comments API should be the primary read path",
62
+ "historyWrite": "System.History",
63
+ "useCommentsApiRead": true,
64
+ "useCommentsApiWrite": false
65
+ }
66
+ },
67
+ "joinMapping": {
68
+ "crmToAdo": {
69
+ "primary": "Custom.ISCRMID",
70
+ "secondary": "System.Title"
71
+ }
72
+ },
73
+ "activity": {
74
+ "activityTypeField": "Custom.activity_type",
75
+ "advisoryValue": "Advisory",
76
+ "defaultState": "02 Active",
77
+ "defaultTags": "#FDE; #recon"
78
+ },
79
+ "queries": {
80
+ "maxSearchResults": 10,
81
+ "partialMatchEnabled": true,
82
+ "partialMatchMinScore": 0.6,
83
+ "searchFields": [
84
+ "System.Title",
85
+ "System.Tags"
86
+ ],
87
+ "defaultOrderBy": "[System.ChangedDate] DESC"
88
+ },
89
+ "linking": {
90
+ "preferredModel": "System.Parent",
91
+ "fallbackAllowed": false,
92
+ "allowPartialMatchSelection": true,
93
+ "candidateLimit": 5
94
+ },
95
+ "discussion": {
96
+ "mode": "history",
97
+ "sourceOfTruthForTranscriptUpdates": true,
98
+ "writeTarget": "pending-decision",
99
+ "contextReadSources": [
100
+ "engagement",
101
+ "activity",
102
+ "allChildren"
103
+ ],
104
+ "includeTranscriptSource": true,
105
+ "maxEntryChars": 8000,
106
+ "dedupe": {
107
+ "enabled": true,
108
+ "keyPattern": "meetingDate|transcriptId",
109
+ "includeWorkItemId": true
110
+ },
111
+ "template": {
112
+ "header": "Meeting Update",
113
+ "sections": [
114
+ "MeetingDate",
115
+ "Participants",
116
+ "Summary",
117
+ "Decisions",
118
+ "Actions",
119
+ "Risks",
120
+ "TranscriptSource"
121
+ ]
122
+ }
123
+ },
124
+ "contextRead": {
125
+ "dualSource": true,
126
+ "preferAdoDiscussion": true,
127
+ "includeCrmDuringMigration": true,
128
+ "childWorkItemDiscovery": {
129
+ "enabled": true,
130
+ "includeAllChildren": true,
131
+ "includeChildDiscussions": true,
132
+ "preferredChildTypeForExecution": "Activity",
133
+ "preferredActivityTypeValue": "Advisory"
134
+ },
135
+ "precedence": [
136
+ "ado.engagement.discussion",
137
+ "ado.children.discussion",
138
+ "ado.activity.discussion",
139
+ "ado.engagement",
140
+ "ado.children",
141
+ "ado.activities",
142
+ "crm.profile",
143
+ "crm.notes",
144
+ "transcript.unposted",
145
+ "external.docs"
146
+ ]
147
+ },
148
+ "validation": {
149
+ "requireExplicitSelectionOnAmbiguousMatch": true,
150
+ "requiredFieldsOnCreate": [
151
+ "System.Title",
152
+ "Custom.activity_type"
153
+ ],
154
+ "requiredFieldsOnLink": [
155
+ "System.Parent"
156
+ ]
157
+ }
158
+ }
159
+ }