kushi-agents 3.12.1 → 3.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,38 @@
1
+ ## Kushi — Project Evidence Agent
2
+
3
+ This workspace has [Kushi](https://gim-home.github.io/kushi/) installed under `.kushi/`. Kushi captures multi-source project evidence (Email, Teams, OneNote, SharePoint, Meetings, CRM, ADO) via **WorkIQ** and answers cited questions over the captured evidence.
4
+
5
+ ### Routing — no `@Kushi` prefix required
6
+
7
+ When the user types any of the following verbs followed by a project name, route to the corresponding Kushi prompt in `.kushi/prompts/` (no `@Kushi` needed):
8
+
9
+ | Verb | Prompt to run |
10
+ | -------------------------- | ------------------------------------------ |
11
+ | `bootstrap <project>` | `/pull-all-bootstrap` |
12
+ | `refresh <project>` | `/pull-all-refresh` |
13
+ | `aggregate <project>` | `/pull-all-aggregate` |
14
+ | `state <project>` | `/rebuild-state` |
15
+ | `consolidate <project>` | `/consolidate-evidence` |
16
+ | `status <project>` | `/show-run-log` |
17
+ | `ask <project> <question>` | `/ask-project` (auto-routes to evidence Q&A) |
18
+
19
+ Project Q&A also auto-dispatches when the user names a known project under the engagement root **and** asks a question about it — no prefix needed.
20
+
21
+ ### Hard rules (enforced by Kushi skills)
22
+
23
+ - **WorkIQ-first** for all M365 sources. `m365_*` / Microsoft Graph calls are only allowed as a classified fallback after a documented WorkIQ failure.
24
+ - **Every assertion** in any Kushi output (State files, Evidence summaries, consolidated reports, Open Questions) MUST carry an inline citation: `[source: <alias>/<folder>/<file> · YYYY-MM-DD]`.
25
+ - **Read-only Q&A** — `ask` never triggers any `pull-*` skill. Stale evidence (>14d) prompts the user to refresh; Kushi does not auto-refresh.
26
+ - **No cross-project bleed** — answers must be grounded only in the named project's `Evidence/` + `State/` + `snapshot/`.
27
+ - **Never reach out** — Kushi does not send Teams/email/CRM-note/ADO-comment on the user's behalf unless they explicitly request it that turn. Recommendations go into the project's Open Questions, not into outbound sends.
28
+
29
+ ### Layout
30
+
31
+ - `.kushi/agents/kushi.agent.md` — orchestrator (also addressable as `@Kushi`)
32
+ - `.kushi/prompts/` — verb prompts (`/pull-*`, `/ask-project`, etc.)
33
+ - `.kushi/skills/` — per-source pull + render skills
34
+ - `.kushi/instructions/` — doctrine (citations, WorkIQ-first, freshness gates)
35
+ - `.kushi/reference-packs/` — bundled domain doctrine (override at project / user / packaged layers)
36
+ - `.kushi/kushi-install.json` — profile manifest written by the installer
37
+
38
+ Full docs: <https://gim-home.github.io/kushi/>
package/bin/cli.mjs CHANGED
@@ -26,6 +26,7 @@ if (args.includes('--help') || args.includes('-h')) {
26
26
  --dest <path> Override destination (relative for vscode, absolute for clawpilot)
27
27
  --force Overwrite existing destination without asking
28
28
  --no-settings Skip .vscode/settings.json update (vscode target only)
29
+ --no-instructions Skip .github/copilot-instructions.md merge (vscode target only)
29
30
  --help, -h Show this help
30
31
 
31
32
  After install, talk to Kushi:
@@ -55,6 +56,7 @@ const options = {
55
56
  dest: getFlag('--dest'),
56
57
  force: args.includes('--force'),
57
58
  noSettings: args.includes('--no-settings'),
59
+ noInstructions: args.includes('--no-instructions'),
58
60
  target,
59
61
  profile: getFlag('--profile'),
60
62
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kushi-agents",
3
- "version": "3.12.1",
3
+ "version": "3.14.0",
4
4
  "description": "Install Kushi — multi-source project evidence agent with snapshot+stream capture across Email, Teams, OneNote, SharePoint, Meetings, CRM, ADO. WorkIQ-only for M365 sources (Graph / m365_* FORBIDDEN as fallbacks; user-paste is first-class). Host-agnostic.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -11,7 +11,8 @@
11
11
  "src/",
12
12
  "plugin/",
13
13
  ".github/config/m365-auth.json.example",
14
- ".github/config/m365-mutable.json.example"
14
+ ".github/config/m365-mutable.json.example",
15
+ ".github/copilot-instructions.kushi.md"
15
16
  ],
16
17
  "engines": {
17
18
  "node": ">=18.0.0"
@@ -8,7 +8,7 @@ applyTo: "**"
8
8
 
9
9
  **A CRM field value is NEVER automatically a confirmed fact.** A Dataverse field update records an internal decision; it does not prove the decision has been (a) communicated to the customer, (b) approved by deal desk / finance / legal, or (c) confirmed in a customer-facing transcript, note, email, or meeting artifact.
10
10
 
11
- Adapted into kushi from Nova doctrine (see `fde-grounding.instructions.md > CRITICAL: CRM Field Values vs. Confirmed Facts`).
11
+ Adapted into kushi from prior doctrine (see `fde-grounding.instructions.md > CRITICAL: CRM Field Values vs. Confirmed Facts`).
12
12
 
13
13
  ## Three states every CRM-sourced assertion must be classified into
14
14
 
@@ -12,7 +12,7 @@ The **engagement-root** is the parent folder that contains all of the user's eng
12
12
  Resolve `<engagement-root>` in this order — first match wins:
13
13
 
14
14
  1. **`<USER_HOME>/.copilot/project-evidence.yml`** — read `engagement_root:` field (preferred).
15
- 2. **`customer_workspace/FDEDocs/`** — if the user's workspace has this symlink, follow it. Common in NOVA-style installs.
15
+ 2. **`customer_workspace/FDEDocs/`** — if the user's workspace has this symlink, follow it. Common in FDE-style installs.
16
16
  3. **Ask the user** once and persist the answer to `<USER_HOME>/.copilot/project-evidence.yml engagement_root`.
17
17
 
18
18
  ## Live config location
@@ -14,7 +14,7 @@ A live Dataverse REST probe afternoon of 2026-05-18 — `GET /new_frontierengine
14
14
  ### Why this was a defect
15
15
  The pull-crm SKILL `Resolution order` section already documented the 4-step sequence (title → account → recent-slice → ask) — but bootstrap was not actually executing it. It appears bootstrap relied on a WorkIQ-only / metadata-only probe that didn't reach Dataverse, then silently wrote `disabled: true`. That is the worst possible disposition: it pretends there is no CRM record, hides the failure from future refreshes, and the project loses CRM evidence entirely.
16
16
 
17
- Adjacent observation: Nova (sibling tool) does this resolution sequence in its bootstrap and would not have missed FE-2026-001791. The doctrine was present in kushi but the execution path was weak.
17
+ Adjacent observation: equivalent sibling tooling performs this resolution sequence in its bootstrap and would not have missed FE-2026-001791. The doctrine was present in kushi but the execution path was weak.
18
18
 
19
19
  ### Fix
20
20
  - New HARD-rule instruction `plugin/instructions/crm-bootstrap-discovery.instructions.md` — `disabled: true` is ONLY allowed after the FULL 4-step REST sequence returns 0 AND the user is presented with top candidates. Auth/reachability failures must leave the boundary EMPTY (with `reason: 'crm-auth-unavailable-<date>'`) so the next refresh retries — NEVER write `disabled: true`.
@@ -98,7 +98,7 @@ The first eight `pull-onenote` versions used prose phrasing (`"sectionFileId <id
98
98
 
99
99
  1. WorkIQ does NOT honor `wdsectionfileid = <id>` as filter syntax — it routes to summary mode AND returns "OneNote internal properties not exposed as searchable fields" refusal text.
100
100
  2. The wdpartid GUIDs we observed in earlier runs were **URL fragments inside SharePoint Doc.aspx hyperlinks** that WorkIQ rendered as response footnotes — not search-index extractor outputs.
101
- 3. The Nova-pattern (natural-language query naming the section + notebook by display name and the page by quoted title) is the actual working pattern. It returned a real verbatim body for the HCA `4/3 - HCA with Jay and Martin` page.
101
+ 3. The natural-language WorkIQ pattern (query naming the section + notebook by display name and the page by quoted title) is the actual working pattern. It returned a real verbatim body for the HCA `4/3 - HCA with Jay and Martin` page.
102
102
  4. **Body retrieval is non-deterministic** — the same 4/3 page returned a verbatim body at 19:42 PDT and `BODY-NOT-EXPOSED` at 19:48 PDT, same query, no edits. The M365 search index's exposure of OneNote bodies oscillates over time.
103
103
  5. **The blocker for months was the WorkIQ EULA.** Without `workiq accept-eula`, every OneNote query silently returns nothing useful. This is a one-time setup step, not a per-call gate.
104
104
 
@@ -112,7 +112,7 @@ The first eight `pull-onenote` versions used prose phrasing (`"sectionFileId <id
112
112
 
113
113
  **HCA result (2026-05-14):** 18 pages enumerated. 1 captured verbatim (4/3). 15 pending retry (BODY-NOT-EXPOSED). 2 enumeration-only (will be probed in Step B on next refresh).
114
114
 
115
- **Key lesson:** when a doctrine is grounded in pattern-matching against tool responses (e.g. "field names route to extractor"), validate it empirically against the live tool BEFORE shipping. The v3.7.8 doctrine was internally consistent and self-citing but never actually tested end-to-end — the 4/3 success that motivated v3.7.9 happened only after honestly retracting v3.7.8 and replicating the Nova workflow step-by-step.
115
+ **Key lesson:** when a doctrine is grounded in pattern-matching against tool responses (e.g. "field names route to extractor"), validate it empirically against the live tool BEFORE shipping. The v3.7.8 doctrine was internally consistent and self-citing but never actually tested end-to-end — the 4/3 success that motivated v3.7.9 happened only after honestly retracting v3.7.8 and replicating the natural-language WorkIQ workflow step-by-step.
116
116
 
117
117
 
118
118
  ## 2026-05-14 — v3.7.9 retraction + v3.8.0 architectural pivot
@@ -342,7 +342,7 @@ Used ONLY when:
342
342
  - The previous run hit auth-required and retry-cooldown has not elapsed (24h), OR
343
343
  - The user explicitly requests WorkIQ-only for diagnostic comparison.
344
344
 
345
- Canonical WorkIQ Step B query (the Nova-pattern):
345
+ Canonical WorkIQ Step B query (natural-language pattern):
346
346
 
347
347
  > Return the FULL readable content verbatim of the page titled `<title>` in my OneNote section `<one_sectionName>` in notebook `<one_notebookName>`. Do not summarize. Do not paraphrase. If the body is not exposed, say so explicitly with the literal phrase `BODY-NOT-EXPOSED` on its own line.
348
348
 
@@ -0,0 +1,80 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ export const KUSHI_BEGIN_MARKER = '<!-- BEGIN kushi-agents (managed — do not edit between markers) -->';
5
+ export const KUSHI_END_MARKER = '<!-- END kushi-agents -->';
6
+
7
+ const MARKER_REGEX = new RegExp(
8
+ `${escapeRegex(KUSHI_BEGIN_MARKER)}[\\s\\S]*?${escapeRegex(KUSHI_END_MARKER)}`,
9
+ );
10
+
11
+ function escapeRegex(str) {
12
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
13
+ }
14
+
15
+ /**
16
+ * Wrap the source content in our managed-block markers.
17
+ * @param {string} source
18
+ * @returns {string}
19
+ */
20
+ function buildBlock(source) {
21
+ return `${KUSHI_BEGIN_MARKER}\n${source.trim()}\n${KUSHI_END_MARKER}\n`;
22
+ }
23
+
24
+ /**
25
+ * Merge the Kushi section into .github/copilot-instructions.md using
26
+ * managed-block markers. Three behaviors:
27
+ *
28
+ * - File missing → create it with a brief header + our block
29
+ * - File has markers → replace block content in place (idempotent upgrade)
30
+ * - File without markers → append our block at the end, preserving user content
31
+ *
32
+ * @param {string} projectRoot
33
+ * @param {string} sourcePkgDir – root of the npm package (for reading the source template)
34
+ * @returns {{ action: 'created' | 'updated-block' | 'appended-block' | 'unchanged', path: string }}
35
+ */
36
+ export function mergeCopilotInstructions(projectRoot, sourcePkgDir) {
37
+ const srcTemplate = path.join(
38
+ sourcePkgDir,
39
+ '.github',
40
+ 'copilot-instructions.kushi.md',
41
+ );
42
+
43
+ if (!fs.existsSync(srcTemplate)) {
44
+ return { action: 'unchanged', path: '', reason: 'source template missing' };
45
+ }
46
+
47
+ const sourceContent = fs.readFileSync(srcTemplate, 'utf-8');
48
+ const block = buildBlock(sourceContent);
49
+
50
+ const githubDir = path.join(projectRoot, '.github');
51
+ const targetPath = path.join(githubDir, 'copilot-instructions.md');
52
+ const displayPath = '.github/copilot-instructions.md';
53
+
54
+ fs.mkdirSync(githubDir, { recursive: true });
55
+
56
+ if (!fs.existsSync(targetPath)) {
57
+ const header = `# Copilot Instructions\n\n_This file is auto-loaded by GitHub Copilot Chat into every chat turn in this workspace._\n\n`;
58
+ fs.writeFileSync(targetPath, header + block, 'utf-8');
59
+ return { action: 'created', path: displayPath };
60
+ }
61
+
62
+ const existing = fs.readFileSync(targetPath, 'utf-8');
63
+
64
+ if (MARKER_REGEX.test(existing)) {
65
+ const updated = existing.replace(MARKER_REGEX, block.trimEnd());
66
+ if (updated === existing) {
67
+ return { action: 'unchanged', path: displayPath };
68
+ }
69
+ fs.writeFileSync(targetPath, updated, 'utf-8');
70
+ return { action: 'updated-block', path: displayPath };
71
+ }
72
+
73
+ const separator = existing.endsWith('\n\n')
74
+ ? ''
75
+ : existing.endsWith('\n')
76
+ ? '\n'
77
+ : '\n\n';
78
+ fs.writeFileSync(targetPath, existing + separator + block, 'utf-8');
79
+ return { action: 'appended-block', path: displayPath };
80
+ }
package/src/main.mjs CHANGED
@@ -14,6 +14,7 @@ import {
14
14
  import { promptForDestination } from './prompt.mjs';
15
15
  import { copyAssets, copyProjectFiles } from './copy-assets.mjs';
16
16
  import { mergeSettings } from './settings.mjs';
17
+ import { mergeCopilotInstructions } from './copilot-instructions.mjs';
17
18
  import {
18
19
  resolveProfile,
19
20
  makeIncludeFilter,
@@ -48,7 +49,7 @@ function resolveProfileForInstall(profileName) {
48
49
 
49
50
  /**
50
51
  * Orchestrator: prompt -> copy -> settings -> summary.
51
- * @param {{ dest?: string, force?: boolean, noSettings?: boolean, target?: string, profile?: string }} options
52
+ * @param {{ dest?: string, force?: boolean, noSettings?: boolean, noInstructions?: boolean, target?: string, profile?: string }} options
52
53
  */
53
54
  export async function main(options = {}) {
54
55
  const version = getVersion();
@@ -150,6 +151,22 @@ async function installVscode(options, resolved, version) {
150
151
  console.log('\n Skipped .vscode/settings.json (--no-settings)');
151
152
  }
152
153
 
154
+ if (!options.noInstructions) {
155
+ const result = mergeCopilotInstructions(projectRoot, PKG_ROOT);
156
+ const labelByAction = {
157
+ 'created': 'Created',
158
+ 'updated-block': 'Updated managed Kushi block in',
159
+ 'appended-block': 'Appended managed Kushi block to existing',
160
+ 'unchanged': 'Unchanged',
161
+ };
162
+ if (result.action !== 'unchanged' || result.path) {
163
+ const label = labelByAction[result.action] || 'Touched';
164
+ console.log(`\n ${label} ${result.path}`);
165
+ }
166
+ } else {
167
+ console.log('\n Skipped .github/copilot-instructions.md (--no-instructions)');
168
+ }
169
+
153
170
  const { copied: projCopied, skipped: projSkipped } = copyProjectFiles(PKG_ROOT);
154
171
  if (projCopied.length > 0) {
155
172
  console.log('\n Copied project files to .github/');
package/src/settings.mjs CHANGED
@@ -4,12 +4,12 @@ import { modify, applyEdits, parse, printParseErrorCode } from 'jsonc-parser';
4
4
  import { SETTINGS_MAP } from './constants.mjs';
5
5
 
6
6
  /**
7
- * Merge Nova settings into .vscode/settings.json (JSONC-safe).
7
+ * Merge Kushi settings into .vscode/settings.json (JSONC-safe).
8
8
  * Creates the file and directory if they don't exist.
9
9
  * Additive-only: never removes existing entries.
10
10
  *
11
11
  * @param {string} projectRoot – the user's project root (cwd)
12
- * @param {string} destination – relative destination path (e.g. ".nova")
12
+ * @param {string} destination – relative destination path (e.g. ".kushi")
13
13
  * @returns {{ created: boolean, keysAdded: string[], keysUnchanged: string[] }}
14
14
  */
15
15
  export function mergeSettings(projectRoot, destination) {