kushi-agents 4.1.0 → 4.2.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.
package/bin/cli.mjs CHANGED
@@ -29,6 +29,13 @@ if (args.includes('--help') || args.includes('-h')) {
29
29
  check (useful for scripted or agent-driven installs)
30
30
  --no-settings Skip .vscode/settings.json update (vscode target only)
31
31
  --no-instructions Skip .github/copilot-instructions.md merge (vscode target only)
32
+
33
+ WorkIQ (REQUIRED — Kushi cannot pull evidence without it):
34
+ --with-workiq Auto-install WorkIQ via winget (Windows) / brew (macOS)
35
+ --workiq-path <abs> Use this explicit path to the workiq binary
36
+ --skip-workiq-check Bypass the WorkIQ pre-flight check (CI / inspection only —
37
+ bootstrap/refresh will block until WorkIQ is installed)
38
+
32
39
  --help, -h Show this help
33
40
 
34
41
  After install, talk to Kushi:
@@ -62,6 +69,9 @@ const options = {
62
69
  noInstructions: args.includes('--no-instructions'),
63
70
  target,
64
71
  profile: getFlag('--profile'),
72
+ withWorkiq: args.includes('--with-workiq'),
73
+ workiqPath: getFlag('--workiq-path'),
74
+ skipWorkiqCheck: args.includes('--skip-workiq-check'),
65
75
  };
66
76
 
67
77
  main(options).catch((err) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kushi-agents",
3
- "version": "4.1.0",
3
+ "version": "4.2.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": {
@@ -0,0 +1,66 @@
1
+ ---
2
+ applyTo: "**"
3
+ description: "Identity auto-resolution — Kushi never asks the user for alias / email / display_name. On the first bootstrap (or whenever those fields are <auto> / placeholder in .kushi/config/project-evidence.yml), Kushi resolves them from WorkIQ in a single call, persists them, and continues. Skipped entirely if the user has set explicit values."
4
+ ---
5
+
6
+ # Identity Resolution — Don't Ask, Probe
7
+
8
+ Kushi must not prompt the user for `alias`, `email`, or `display_name`. These are derivable from WorkIQ in one call, and asking is poor UX.
9
+
10
+ ## When to resolve
11
+
12
+ On the **first step of every prompt** that reads contributor identity (bootstrap, refresh, aggregate, ask, fde-*, propose-ado, apply-ado), check `<workspace>/.kushi/config/project-evidence.yml`:
13
+
14
+ * If `alias`, `email`, or `display_name` is **missing**, set to `<auto>`, or matches a placeholder pattern (`<your-alias>`, `<Your Full Name>`, `your.email@example.com`) → **resolve from WorkIQ**.
15
+ * If all three are explicit non-placeholder values → **skip**. Respect the user's override.
16
+
17
+ ## How to resolve
18
+
19
+ Single WorkIQ call:
20
+
21
+ ```bash
22
+ workiq ask -q "Who am I? Return my UPN (email), display name, and mail nickname as JSON: {\"upn\":\"...\",\"displayName\":\"...\",\"mailNickname\":\"...\"}"
23
+ ```
24
+
25
+ Map the response:
26
+
27
+ | Config field | Source | Fallback if missing |
28
+ |----------------|-------------------------------------|--------------------------------------|
29
+ | `email` | `upn` | Block — must be present |
30
+ | `display_name` | `displayName` | Same as `alias` |
31
+ | `alias` | `mailNickname` | `upn.split('@')[0]` (lowercase) |
32
+
33
+ ## After resolution
34
+
35
+ 1. **Persist back** to `<workspace>/.kushi/config/project-evidence.yml` (preserving comments + indentation). The user sees the resolved values on next open; no surprise.
36
+ 2. **Echo back** to the user, one line:
37
+ > ✓ Identity: `Alex Smith <alex@microsoft.com>` (alias=`alex`). Edit `.kushi/config/project-evidence.yml` to override.
38
+ 3. **Continue** the prompt.
39
+
40
+ ## Failure modes
41
+
42
+ | Scenario | Behavior |
43
+ |-----------------------------------------------|------------------------------------------------------------------------------------------------------------------------------|
44
+ | WorkIQ returns auth error | Block: "Sign in to WorkIQ first: `workiq accept-eula && workiq ask -q ping`". Do not proceed. |
45
+ | WorkIQ returns empty / NO_RESULTS | Block: "WorkIQ could not resolve your identity. Try `workiq ask -q 'who am I'` and retry." |
46
+ | WorkIQ binary missing | This should already have been caught by the installer's pre-flight. If reached, block with the same install hint. |
47
+ | User has explicit non-placeholder values | Skip resolution entirely. Never overwrite user-set values. |
48
+ | Alias collision with another contributor | Bootstrap detects existing `Evidence/<alias>/` whose `contributors.yml` records a different email → ask the user to disambiguate (suggest `<alias>-<tenant-prefix>` e.g. `alex-ms`). |
49
+
50
+ ## What NOT to do
51
+
52
+ * Do NOT ask the user `What alias should Kushi use?`. The legacy onboarding prompt is gone.
53
+ * Do NOT call `m365_*` / Graph as a fallback. WorkIQ is the single source of identity truth (`workiq-first.instructions.md`).
54
+ * Do NOT resolve on every run. Once persisted, the config values are authoritative.
55
+
56
+ ## Integration with other doctrine
57
+
58
+ * `workiq-first.instructions.md` — identity resolution is the canonical example of WorkIQ-first. Add to the inventory.
59
+ * `bootstrap-project` SKILL — Step 0 is identity resolution. Step 1 is project context.
60
+ * `tracking.instructions.md` — the resolved identity goes into the tracking artifact's frontmatter under `actor:`.
61
+
62
+ ## References
63
+
64
+ * `workiq-first.instructions.md` — the parent doctrine.
65
+ * `engagement-root-resolution.instructions.md` — `projects_root` resolution (separate from identity).
66
+ * `templates/init/project-evidence.template.yml` — defaults to `<auto>` for these three fields.
@@ -28,6 +28,7 @@ Applies to ALL evidence retrieval from these M365 sources:
28
28
  - Email bodies and attachments
29
29
  - SharePoint file contents (when text extraction is needed)
30
30
  - Calendar events (when not already known by id)
31
+ - **Contributor identity** (UPN / displayName / mailNickname) — see `identity-resolution.instructions.md` for the canonical "who am I?" probe.
31
32
 
32
33
  **Out of scope** (these are NOT WorkIQ — they remain on their direct paths):
33
34
 
@@ -35,10 +35,25 @@ After every run (success or coverage-gaps), write `<project>/bootstrap-status.md
35
35
 
36
36
  - `<project>` — engagement name (fuzzy-matched per `engagement-root-resolution.instructions.md`).
37
37
  - `<window>` — defaults to **last 30 days** (override with `last N days` / `since <date>`).
38
- - Implicit: current contributor `<alias>` (from personal config).
38
+ - Implicit: current contributor `<alias>` (resolved in Step 0 — see below).
39
39
 
40
40
  ## Steps
41
41
 
42
+ ### Step 0 — Identity resolution (REQUIRED, never asks the user)
43
+
44
+ Per `identity-resolution.instructions.md`. Read `<workspace>/.kushi/config/project-evidence.yml`:
45
+
46
+ * If `alias`, `email`, or `display_name` is missing / `<auto>` / matches a placeholder → call WorkIQ once:
47
+ ```
48
+ workiq ask -q "Who am I? Return UPN, displayName, mailNickname as JSON."
49
+ ```
50
+ Map `upn → email`, `displayName → display_name`, `mailNickname → alias` (fallback `email.split('@')[0]`).
51
+ * Persist resolved values back to the YAML (preserve comments).
52
+ * Echo one line: `✓ Identity: <displayName> <<UPN>> (alias=<alias>). Edit .kushi/config/project-evidence.yml to override.`
53
+ * If all three are already explicit non-placeholder values → skip silently.
54
+
55
+ Hard stop if WorkIQ returns auth error or empty — print the WorkIQ sign-in hint and exit. Never fall back to asking the user.
56
+
42
57
  ### Step 1 — Machine preflight (SETUP)
43
58
 
44
59
  Verify in order. Stop on hard failures.
@@ -5,14 +5,23 @@
5
5
  # maintains their own. Nothing here is a secret, but it IS personal —
6
6
  # add `.kushi/config/` to .gitignore before committing the rest of .kushi/.
7
7
  #
8
- # Edit the values below (lines marked FILL ME IN), then run `bootstrap <project>`.
8
+ # IDENTITY auto-detected on first bootstrap.
9
+ # On the first run of `bootstrap <project>`, Kushi asks WorkIQ "who am I?"
10
+ # and fills these three fields in for you (UPN, displayName, mailNickname).
11
+ # You only need to override them if you want a different folder name or
12
+ # a friendlier display label. Leave them at the placeholder values to
13
+ # trigger auto-detection.
9
14
 
10
- # FILL ME IN — short id used as your evidence subfolder name (e.g. "alex", "jordan").
11
- alias: <your-alias>
15
+ # Optional override — short id used as your Evidence/ subfolder name.
16
+ # Leave as <auto> to derive from the part before "@" in your email.
17
+ alias: <auto>
12
18
 
13
- # FILL ME IN
14
- display_name: <Your Full Name>
15
- email: your.email@example.com
19
+ # Optional override — friendly label used in run logs.
20
+ # Leave as <auto> to pull from WorkIQ's displayName.
21
+ display_name: <auto>
22
+
23
+ # Optional override — your work email. Leave as <auto> to pull from WorkIQ.
24
+ email: <auto>
16
25
 
17
26
  # FILL ME IN — where your engagement-root lives. The parent folder containing
18
27
  # one subfolder per project (typically synced from a team SharePoint library).
@@ -35,4 +44,5 @@ active_projects:
35
44
  # 2. `workiq` on PATH
36
45
  # 3. ~/.kushi/bin/workiq.cmd (Windows) / ~/.kushi/bin/workiq (Linux/macOS)
37
46
  # workiq:
38
- # cli_path: 'C:\Users\<you>\.kushi\bin\workiq.cmd'
47
+ # cli_path: 'C:\Users\<you>\.kushi\bin\workiq.cmd'
48
+
@@ -0,0 +1,125 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { existsSync, statSync } from 'node:fs';
3
+ import path from 'node:path';
4
+ import os from 'node:os';
5
+
6
+ /**
7
+ * Probe for a working WorkIQ install.
8
+ *
9
+ * Resolution order:
10
+ * 1. explicit absolute path supplied via --workiq-path / opts.workiqPath
11
+ * 2. `workiq` on PATH (via `where` on Windows, `command -v` elsewhere)
12
+ * 3. Kushi-managed bin: ~/.kushi/bin/workiq.cmd (Win) or ~/.kushi/bin/workiq
13
+ *
14
+ * Returns:
15
+ * { ok: true, path, version } — usable WorkIQ found
16
+ * { ok: false, reason, hint } — not found / not runnable
17
+ *
18
+ * Reasons:
19
+ * 'not-found' — no workiq binary anywhere we looked
20
+ * 'path-invalid' — explicit --workiq-path was given but the file doesn't exist
21
+ * 'not-executable' — found but `--version` failed (corrupt install, missing deps, etc.)
22
+ */
23
+ export function checkWorkIQ(opts = {}) {
24
+ const isWin = process.platform === 'win32';
25
+ const homeBin = path.join(
26
+ os.homedir(),
27
+ '.kushi',
28
+ 'bin',
29
+ isWin ? 'workiq.cmd' : 'workiq',
30
+ );
31
+
32
+ if (opts.workiqPath) {
33
+ const abs = path.resolve(opts.workiqPath);
34
+ if (!existsSync(abs) || !statSync(abs).isFile()) {
35
+ return {
36
+ ok: false,
37
+ reason: 'path-invalid',
38
+ hint: `--workiq-path "${opts.workiqPath}" does not point at an existing file.`,
39
+ };
40
+ }
41
+ return runVersion(abs);
42
+ }
43
+
44
+ const onPath = findOnPath('workiq');
45
+ if (onPath) return runVersion(onPath);
46
+
47
+ if (existsSync(homeBin) && statSync(homeBin).isFile()) {
48
+ return runVersion(homeBin);
49
+ }
50
+
51
+ return {
52
+ ok: false,
53
+ reason: 'not-found',
54
+ hint: installHint(),
55
+ };
56
+ }
57
+
58
+ function findOnPath(name) {
59
+ const isWin = process.platform === 'win32';
60
+ const cmd = isWin ? 'where' : 'command';
61
+ const args = isWin ? [name] : ['-v', name];
62
+ const res = spawnSync(cmd, args, { encoding: 'utf-8', shell: !isWin });
63
+ if (res.status !== 0) return null;
64
+ const first = (res.stdout || '').split(/\r?\n/).map((s) => s.trim()).find(Boolean);
65
+ return first || null;
66
+ }
67
+
68
+ function runVersion(binPath) {
69
+ const res = spawnSync(binPath, ['--version'], { encoding: 'utf-8', timeout: 10_000 });
70
+ if (res.status !== 0) {
71
+ return {
72
+ ok: false,
73
+ reason: 'not-executable',
74
+ hint: `Found WorkIQ at ${binPath} but \`workiq --version\` failed (exit ${res.status}). Try reinstalling. ${installHint()}`,
75
+ };
76
+ }
77
+ const version = (res.stdout || res.stderr || '').trim().split(/\r?\n/)[0] || 'unknown';
78
+ return { ok: true, path: binPath, version };
79
+ }
80
+
81
+ function installHint() {
82
+ const plat = process.platform;
83
+ if (plat === 'win32') {
84
+ return 'Install with: winget install Microsoft.WorkIQ';
85
+ }
86
+ if (plat === 'darwin') {
87
+ return 'Install with: brew install --cask microsoft-workiq';
88
+ }
89
+ return 'See https://gim-home.github.io/kushi/getting-started/install-workiq/ for Linux instructions.';
90
+ }
91
+
92
+ /**
93
+ * Best-effort auto-install via the platform's package manager.
94
+ * Only invoked when --with-workiq is passed. Non-blocking on failure —
95
+ * caller decides whether to hard-fail.
96
+ *
97
+ * Returns { ok, reason, output }.
98
+ */
99
+ export function tryInstallWorkIQ() {
100
+ const plat = process.platform;
101
+ let cmd, args;
102
+ if (plat === 'win32') {
103
+ cmd = 'winget';
104
+ args = ['install', '--id', 'Microsoft.WorkIQ', '-e', '--accept-package-agreements', '--accept-source-agreements'];
105
+ } else if (plat === 'darwin') {
106
+ cmd = 'brew';
107
+ args = ['install', '--cask', 'microsoft-workiq'];
108
+ } else {
109
+ return {
110
+ ok: false,
111
+ reason: 'unsupported-platform',
112
+ output: 'Auto-install is not supported on Linux. See https://gim-home.github.io/kushi/getting-started/install-workiq/',
113
+ };
114
+ }
115
+
116
+ const res = spawnSync(cmd, args, { encoding: 'utf-8', stdio: 'inherit' });
117
+ if (res.error || res.status !== 0) {
118
+ return {
119
+ ok: false,
120
+ reason: 'install-failed',
121
+ output: res.error ? res.error.message : `${cmd} exited with status ${res.status}`,
122
+ };
123
+ }
124
+ return { ok: true, output: `${cmd} ${args.join(' ')} succeeded` };
125
+ }
package/src/main.mjs CHANGED
@@ -17,6 +17,7 @@ import { copyAssets, copyProjectFiles } from './copy-assets.mjs';
17
17
  import { mergeSettings } from './settings.mjs';
18
18
  import { mergeCopilotInstructions } from './copilot-instructions.mjs';
19
19
  import { seedConfig } from './seed-config.mjs';
20
+ import { checkWorkIQ, tryInstallWorkIQ } from './check-workiq.mjs';
20
21
  import {
21
22
  resolveProfile,
22
23
  makeIncludeFilter,
@@ -71,6 +72,9 @@ export async function main(options = {}) {
71
72
  console.log(` Profile chain: ${resolved.chain.join(' -> ')}`);
72
73
  console.log(` ${resolved.description}\n`);
73
74
 
75
+ // Hard prerequisite: WorkIQ. Kushi cannot pull evidence without it.
76
+ await preflightWorkIQ(options);
77
+
74
78
  if (target === TARGET_CLAWPILOT) {
75
79
  await installClawpilot(options, resolved, version);
76
80
  } else {
@@ -290,6 +294,56 @@ async function confirmOverwriteIfExists(fullDest, displayDest, force) {
290
294
  }
291
295
  }
292
296
 
297
+ /**
298
+ * Hard prerequisite: WorkIQ must be installed and runnable before Kushi will
299
+ * copy any assets. Kushi's pull-* skills are useless without it. Three modes:
300
+ *
301
+ * --skip-workiq-check → print a warning and continue (CI / asset-inspection only)
302
+ * --with-workiq → attempt auto-install via winget/brew first, then re-check
303
+ * default → probe; on failure, print install hint and exit 1
304
+ *
305
+ * Always prints the resolved WorkIQ path + version on success so users have a
306
+ * record in their terminal scrollback.
307
+ */
308
+ async function preflightWorkIQ(options) {
309
+ if (options.skipWorkiqCheck) {
310
+ console.warn(
311
+ ' ⚠️ WorkIQ pre-flight skipped (--skip-workiq-check).',
312
+ );
313
+ console.warn(
314
+ ' Kushi will install, but bootstrap/refresh will block until WorkIQ is present.\n',
315
+ );
316
+ return;
317
+ }
318
+
319
+ let result = checkWorkIQ({ workiqPath: options.workiqPath });
320
+
321
+ if (!result.ok && options.withWorkiq) {
322
+ console.log(' WorkIQ not found — attempting auto-install (--with-workiq)…\n');
323
+ const ins = tryInstallWorkIQ();
324
+ if (!ins.ok) {
325
+ console.error(`\n ✖ Auto-install failed: ${ins.output}\n`);
326
+ console.error(` ${result.hint || ''}\n`);
327
+ process.exit(1);
328
+ }
329
+ result = checkWorkIQ({ workiqPath: options.workiqPath });
330
+ }
331
+
332
+ if (!result.ok) {
333
+ console.error('\n ✖ WorkIQ is required and was not found.\n');
334
+ console.error(' Kushi cannot capture evidence without WorkIQ. Install it first:\n');
335
+ console.error(` ${result.hint}\n`);
336
+ console.error(' Then run `workiq ask -q "ping"` to confirm sign-in, and re-run this installer.\n');
337
+ console.error(' Escape hatches (NOT recommended):');
338
+ console.error(' • --workiq-path <abs> supply a non-standard install path');
339
+ console.error(' • --with-workiq let the installer try winget/brew');
340
+ console.error(' • --skip-workiq-check install assets anyway (bootstrap will still block)\n');
341
+ process.exit(1);
342
+ }
343
+
344
+ console.log(` ✓ WorkIQ detected at ${result.path} (${result.version})\n`);
345
+ }
346
+
293
347
  /**
294
348
  * Detect whether the cwd looks like a sane install target and, if not, print
295
349
  * an actionable message. Three cases: