kushi-agents 4.4.0 → 4.4.3
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 +2 -4
- package/package.json +1 -1
- package/plugin/agents/kushi.agent.md +5 -1
- package/plugin/instructions/ado-engagement-tree.instructions.md +1 -1
- package/plugin/instructions/az-auth-conditional.instructions.md +2 -2
- package/plugin/instructions/azure-auth-patterns.instructions.md +6 -6
- package/plugin/instructions/bootstrap-status-format.instructions.md +15 -2
- package/plugin/instructions/cleanup-on-resolution.instructions.md +1 -1
- package/plugin/instructions/communication-guidelines.instructions.md +79 -0
- package/plugin/instructions/crm-bootstrap-discovery.instructions.md +1 -1
- package/plugin/instructions/deferred-retry-on-workiq-fail.instructions.md +155 -0
- package/plugin/instructions/engagement-root-resolution.instructions.md +13 -10
- package/plugin/instructions/evidence-layout-canonical.instructions.md +2 -2
- package/plugin/instructions/fallback-status-reporting.instructions.md +104 -0
- package/plugin/instructions/fde-grounding.instructions.md +146 -0
- package/plugin/instructions/identity-resolution.instructions.md +15 -8
- package/plugin/instructions/m365-id-registry.instructions.md +1 -1
- package/plugin/instructions/scope-boundaries.instructions.md +4 -4
- package/plugin/instructions/status-taxonomy.instructions.md +118 -0
- package/plugin/instructions/workiq-only.instructions.md +4 -2
- package/plugin/lib/Get-KushiConfig.ps1 +112 -7
- package/plugin/prompts/bootstrap.prompt.md +64 -49
- package/plugin/skills/apply-ado-update/SKILL.md +2 -2
- package/plugin/skills/bootstrap-project/SKILL.md +10 -10
- package/plugin/skills/propose-ado-update/SKILL.md +3 -3
- package/plugin/skills/pull-ado/SKILL.md +27 -13
- package/plugin/skills/pull-crm/SKILL.md +20 -9
- package/plugin/skills/pull-email/SKILL.md +2 -2
- package/plugin/skills/pull-meetings/SKILL.md +1 -1
- package/plugin/skills/pull-onenote/scripts/recapture-section-url.mjs +2 -1
- package/plugin/skills/pull-onenote/write-snapshot.mjs +2 -1
- package/plugin/skills/pull-sharepoint/SKILL.md +1 -1
- package/plugin/skills/pull-teams/SKILL.md +1 -1
- package/plugin/skills/refresh-project/SKILL.md +21 -1
- package/plugin/skills/self-check/run.ps1 +24 -1
- package/plugin/templates/ado-update/integrations-ado-writes.example.yml +1 -1
- package/plugin/templates/init/integrations.template.yml +32 -19
- package/plugin/templates/init/m365-auth.template.json +5 -5
- package/plugin/templates/init/project-integrations.template.yml +2 -2
- package/plugin/templates/snapshot/onenote-page.template.md +3 -3
- package/src/config-loader.mjs +92 -5
- package/src/main.mjs +2 -30
- package/plugin/templates/init/ado-config.template.yml +0 -21
- package/plugin/templates/init/crm-config.template.yml +0 -16
- package/src/prompt.mjs +0 -42
|
@@ -15,16 +15,16 @@
|
|
|
15
15
|
"dataverse": "https://iscrm.crm.dynamics.com"
|
|
16
16
|
},
|
|
17
17
|
"oneNote": {
|
|
18
|
-
"defaultNotebookName": "",
|
|
19
|
-
"defaultNotebookId": "",
|
|
18
|
+
"defaultNotebookName": "__FILL_ME_IN__",
|
|
19
|
+
"defaultNotebookId": "__FILL_ME_IN__",
|
|
20
20
|
"defaultSectionResolverUrl": "",
|
|
21
21
|
"defaultNotebookRootLink": "",
|
|
22
|
-
"defaultLinkOwner": ""
|
|
22
|
+
"defaultLinkOwner": "__FILL_ME_IN__"
|
|
23
23
|
},
|
|
24
24
|
"emailContext": {
|
|
25
25
|
"enabled": true,
|
|
26
26
|
"dateFloor": "",
|
|
27
|
-
"folders": [],
|
|
27
|
+
"folders": ["__FILL_ME_IN__"],
|
|
28
28
|
"includeSubfolders": true,
|
|
29
29
|
"sourceCoverageLabel": "",
|
|
30
30
|
"matchingPolicy": {
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
},
|
|
57
57
|
"sharePointContext": {
|
|
58
58
|
"enabled": true,
|
|
59
|
-
"localProjectsRoot": "",
|
|
59
|
+
"localProjectsRoot": "__FILL_ME_IN__",
|
|
60
60
|
"matchingPolicy": {
|
|
61
61
|
"mode": "fuzzy-folder-name",
|
|
62
62
|
"rankingOrder": ["exact", "prefix", "contains"],
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
# AND declares the explicit boundaries inside which Kushi is allowed to query.
|
|
4
4
|
#
|
|
5
5
|
# Connection / auth / field mappings are NOT here — those are global at:
|
|
6
|
-
# <
|
|
7
|
-
# <
|
|
6
|
+
# <workspace>/.kushi/config/shared/integrations.yml
|
|
7
|
+
# <workspace>/.kushi/config/shared/integrations.yml
|
|
8
8
|
#
|
|
9
9
|
# Boundaries doctrine: see plugin/instructions/scope-boundaries.instructions.md.
|
|
10
10
|
# Required boundary keys MUST be filled or the source is disabled (not silently widened).
|
|
@@ -20,7 +20,7 @@ captured_at: "<ISO-8601 if captured/user-pasted, else empty>"
|
|
|
20
20
|
- **Capture status:** `<last_status>`
|
|
21
21
|
- **Attempts:** <int>
|
|
22
22
|
- **Last attempt:** <ts>
|
|
23
|
-
- **Source basis:** WorkIQ enumerate + per-page verbatim probe (pull-onenote v2.5.0). Graph `/me/onenote/*` skipped per workspace policy. Per-page retry registry: `.
|
|
23
|
+
- **Source basis:** WorkIQ enumerate + per-page verbatim probe (pull-onenote v2.5.0). Graph `/me/onenote/*` skipped per workspace policy. Per-page retry registry: `.kushi/config/user/m365-mutable.json#knownSections.<projectKey>.one_pages`.
|
|
24
24
|
|
|
25
25
|
<!-- IF last_status == "captured" or "user-pasted" -->
|
|
26
26
|
|
|
@@ -43,7 +43,7 @@ captured_at: "<ISO-8601 if captured/user-pasted, else empty>"
|
|
|
43
43
|
|
|
44
44
|
❌ **page-body-unavailable: WorkIQ returned BODY-NOT-EXPOSED on attempt <N>.**
|
|
45
45
|
|
|
46
|
-
> Per `pull-onenote` v2.5.0 § "Empirical contract": WorkIQ OneNote body retrieval is non-deterministic — the same page may return a body on a later refresh. This page IS registered for retry in `.
|
|
46
|
+
> Per `pull-onenote` v2.5.0 § "Empirical contract": WorkIQ OneNote body retrieval is non-deterministic — the same page may return a body on a later refresh. This page IS registered for retry in `.kushi/config/user/m365-mutable.json#knownSections.<projectKey>.one_pages` and will be re-attempted on every refresh until `last_status` becomes `captured` or `user-pasted`. Inferring the page contents from adjacent emails or chat traffic is forbidden.
|
|
47
47
|
|
|
48
48
|
### next_step
|
|
49
49
|
|
|
@@ -93,5 +93,5 @@ Per `thoroughness-detector.instructions.md`, the writer must tick all that apply
|
|
|
93
93
|
- [ ] If `last_status == captured` or `user-pasted`: AI Narrative Summary (3+ paragraphs) AND verbatim Body section are present and non-empty
|
|
94
94
|
- [ ] If `last_status == BODY-NOT-EXPOSED`: explicit marker, `next_step` block, and metadata table all present; NO inferred or paraphrased body content
|
|
95
95
|
- [ ] If `last_status == enumeration-only`: explicit transient marker present; NO body content of any kind
|
|
96
|
-
- [ ] Page is registered in `.
|
|
96
|
+
- [ ] Page is registered in `.kushi/config/user/m365-mutable.json#knownSections.<projectKey>.one_pages` with matching `last_status` and `attempts`
|
|
97
97
|
- [ ] No fabrication: nothing in this file came from adjacent emails, chats, or other sources to "fill in" what WorkIQ did not return
|
package/src/config-loader.mjs
CHANGED
|
@@ -1,6 +1,74 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Schema-aware required-field map (kushi v4.4.1+).
|
|
6
|
+
* For each logical config name, list the dot-paths that MUST be populated
|
|
7
|
+
* (non-empty, non-sentinel) before the config is considered usable.
|
|
8
|
+
*/
|
|
9
|
+
const REQUIRED_FIELDS = {
|
|
10
|
+
'm365-auth': [
|
|
11
|
+
'm365Auth.defaultTenantId',
|
|
12
|
+
'm365Auth.oneNote.defaultNotebookName',
|
|
13
|
+
'm365Auth.oneNote.defaultNotebookId',
|
|
14
|
+
'm365Auth.oneNote.defaultLinkOwner',
|
|
15
|
+
'm365Auth.emailContext.folders',
|
|
16
|
+
'm365Auth.sharePointContext.localProjectsRoot',
|
|
17
|
+
],
|
|
18
|
+
'project-evidence': [
|
|
19
|
+
'identity.alias',
|
|
20
|
+
'identity.email',
|
|
21
|
+
'engagement_root',
|
|
22
|
+
],
|
|
23
|
+
'integrations': [],
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const SENTINELS = [
|
|
27
|
+
'__FILL_ME_IN__',
|
|
28
|
+
'<auto>',
|
|
29
|
+
'<TENANT_ID>',
|
|
30
|
+
'<NOTEBOOK_ID>',
|
|
31
|
+
'<NOTEBOOK_NAME>',
|
|
32
|
+
'<LINK_OWNER>',
|
|
33
|
+
'<SP_LOCAL_ROOT>',
|
|
34
|
+
'<your-alias>',
|
|
35
|
+
'<Your Full Name>',
|
|
36
|
+
'your.email@example.com',
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
function isSentinel(v) {
|
|
40
|
+
if (v === null || v === undefined) return true;
|
|
41
|
+
if (typeof v !== 'string') return false;
|
|
42
|
+
if (v.trim() === '') return true;
|
|
43
|
+
return SENTINELS.some(s => v.includes(s));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function getDotPath(obj, dotPath) {
|
|
47
|
+
let cur = obj;
|
|
48
|
+
for (const seg of dotPath.split('.')) {
|
|
49
|
+
if (cur === null || cur === undefined) return undefined;
|
|
50
|
+
cur = cur[seg];
|
|
51
|
+
}
|
|
52
|
+
return cur;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function checkRequired(name, parsed) {
|
|
56
|
+
const required = REQUIRED_FIELDS[name] || [];
|
|
57
|
+
const missing = [];
|
|
58
|
+
for (const field of required) {
|
|
59
|
+
const v = getDotPath(parsed, field);
|
|
60
|
+
if (v === null || v === undefined) { missing.push(field); continue; }
|
|
61
|
+
if (Array.isArray(v)) {
|
|
62
|
+
if (v.length === 0 || v.every(x => typeof x === 'string' && isSentinel(x))) {
|
|
63
|
+
missing.push(field);
|
|
64
|
+
}
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (typeof v === 'string' && isSentinel(v)) missing.push(field);
|
|
68
|
+
}
|
|
69
|
+
return missing;
|
|
70
|
+
}
|
|
71
|
+
|
|
4
72
|
/**
|
|
5
73
|
* Node-side twin of Get-KushiConfig.ps1.
|
|
6
74
|
*
|
|
@@ -44,18 +112,37 @@ export function loadKushiConfig(name, opts = {}) {
|
|
|
44
112
|
const raw = fs.readFileSync(resolved, 'utf8');
|
|
45
113
|
|
|
46
114
|
if (!opts.allowPlaceholders) {
|
|
47
|
-
if (
|
|
115
|
+
if (SENTINELS.some(s => raw.includes(s))) {
|
|
48
116
|
throw new Error(
|
|
49
|
-
`Kushi config '${resolved}' still has template placeholders
|
|
117
|
+
`Kushi config '${resolved}' still has template placeholders ` +
|
|
118
|
+
`(e.g. __FILL_ME_IN__, <auto>, <TENANT_ID>). ` +
|
|
50
119
|
`Edit it with real values, or pass {allowPlaceholders: true}.`
|
|
51
120
|
);
|
|
52
121
|
}
|
|
53
122
|
}
|
|
54
123
|
|
|
55
124
|
if (opts.raw) return raw;
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
125
|
+
|
|
126
|
+
let parsed;
|
|
127
|
+
if (ext === 'json') {
|
|
128
|
+
parsed = JSON.parse(raw);
|
|
129
|
+
} else {
|
|
130
|
+
// YAML: return raw — Node has no built-in parser. Caller installs js-yaml if needed.
|
|
131
|
+
return raw;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (!opts.allowPlaceholders) {
|
|
135
|
+
const missing = checkRequired(name, parsed);
|
|
136
|
+
if (missing.length > 0) {
|
|
137
|
+
throw new Error(
|
|
138
|
+
`Kushi config '${resolved}' is missing required fields:\n` +
|
|
139
|
+
missing.map(f => ` - ${f}`).join('\n') + '\n' +
|
|
140
|
+
`Edit it with real values, or pass {allowPlaceholders: true}.`
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return parsed;
|
|
59
146
|
}
|
|
60
147
|
|
|
61
148
|
function defaultExt(name) {
|
package/src/main.mjs
CHANGED
|
@@ -12,7 +12,6 @@ import {
|
|
|
12
12
|
PLUGIN_SOURCE_DIR,
|
|
13
13
|
PROJECT_MARKERS,
|
|
14
14
|
} from './constants.mjs';
|
|
15
|
-
import { promptForDestination } from './prompt.mjs';
|
|
16
15
|
import { copyAssets } from './copy-assets.mjs';
|
|
17
16
|
import { mergeSettings } from './settings.mjs';
|
|
18
17
|
import { mergeCopilotInstructions } from './copilot-instructions.mjs';
|
|
@@ -92,32 +91,7 @@ async function installVscode(options, resolved, version) {
|
|
|
92
91
|
warnIfNotProjectRoot(projectRoot);
|
|
93
92
|
}
|
|
94
93
|
|
|
95
|
-
|
|
96
|
-
if (options.dest) {
|
|
97
|
-
dest = options.dest.replace(/\\/g, '/');
|
|
98
|
-
if (path.isAbsolute(dest)) {
|
|
99
|
-
console.error('\n Please use a relative path (e.g., .kushi).\n');
|
|
100
|
-
process.exit(1);
|
|
101
|
-
}
|
|
102
|
-
dest = dest.replace(/\/+$/, '');
|
|
103
|
-
if (!dest) {
|
|
104
|
-
console.error('\n Please provide a non-empty destination path.\n');
|
|
105
|
-
process.exit(1);
|
|
106
|
-
}
|
|
107
|
-
const resolvedPath = path.resolve(projectRoot, dest);
|
|
108
|
-
if (
|
|
109
|
-
!resolvedPath.startsWith(projectRoot + path.sep) &&
|
|
110
|
-
resolvedPath !== projectRoot
|
|
111
|
-
) {
|
|
112
|
-
console.error('\n Destination must be within the current project.\n');
|
|
113
|
-
process.exit(1);
|
|
114
|
-
}
|
|
115
|
-
} else if (options.yes) {
|
|
116
|
-
dest = DEFAULT_DEST;
|
|
117
|
-
} else {
|
|
118
|
-
dest = await promptForDestination(DEFAULT_DEST);
|
|
119
|
-
}
|
|
120
|
-
|
|
94
|
+
const dest = DEFAULT_DEST;
|
|
121
95
|
const fullDest = path.resolve(projectRoot, dest);
|
|
122
96
|
await confirmOverwriteIfExists(fullDest, dest, options.force);
|
|
123
97
|
|
|
@@ -182,9 +156,7 @@ async function installVscode(options, resolved, version) {
|
|
|
182
156
|
*/
|
|
183
157
|
async function installClawpilot(options, resolved, version) {
|
|
184
158
|
const home = os.homedir();
|
|
185
|
-
const fullDest =
|
|
186
|
-
? path.resolve(options.dest)
|
|
187
|
-
: path.join(home, ...CLAWPILOT_DEST_SUBPATH.split('/'));
|
|
159
|
+
const fullDest = path.join(home, ...CLAWPILOT_DEST_SUBPATH.split('/'));
|
|
188
160
|
|
|
189
161
|
const displayDest = fullDest.startsWith(home + path.sep)
|
|
190
162
|
? '~' + path.sep + path.relative(home, fullDest)
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
# Azure DevOps connection — per-user filled. Lives at:
|
|
2
|
-
# <engagement-root>/.project-evidence/ado/config.yml
|
|
3
|
-
# NOT in repo. NOT in `.kushi/`.
|
|
4
|
-
|
|
5
|
-
tenantId: "72f988bf-86f1-41af-91ab-2d7cd011db47" # Microsoft tenant
|
|
6
|
-
organization: "https://dev.azure.com/<your-org>"
|
|
7
|
-
defaultProject: "<your-default-ado-project>"
|
|
8
|
-
apiVersion: "7.1"
|
|
9
|
-
|
|
10
|
-
# ADO scope GUID — fixed across tenants
|
|
11
|
-
azDevOpsResourceId: "499b84ac-1321-427f-aa17-267ca6975798"
|
|
12
|
-
|
|
13
|
-
# Saved queries / area paths can be overridden per project in
|
|
14
|
-
# <engagement-root>/<project>/integrations.yml under `ado:`
|
|
15
|
-
defaults:
|
|
16
|
-
queryId: ""
|
|
17
|
-
areaPath: ""
|
|
18
|
-
iterationPath: ""
|
|
19
|
-
|
|
20
|
-
auth:
|
|
21
|
-
strategy: "az-cli"
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
# CRM (Dataverse) connection — per-user filled. Lives at:
|
|
2
|
-
# <engagement-root>/.project-evidence/crm/config.yml
|
|
3
|
-
# NOT in repo. NOT in `.kushi/`.
|
|
4
|
-
|
|
5
|
-
tenantId: "72f988bf-86f1-41af-91ab-2d7cd011db47" # Microsoft tenant
|
|
6
|
-
environmentUrl: "https://<your-org>.crm.dynamics.com"
|
|
7
|
-
apiVersion: "9.2"
|
|
8
|
-
|
|
9
|
-
# Default entity to query. Override per project in
|
|
10
|
-
# <engagement-root>/<project>/integrations.yml
|
|
11
|
-
defaultEntitySet: "msdyn_engagements"
|
|
12
|
-
|
|
13
|
-
# Authentication strategy. Currently only "az-cli" supported.
|
|
14
|
-
# kushi will run: az account get-access-token --resource <environmentUrl> --tenant <tenantId>
|
|
15
|
-
auth:
|
|
16
|
-
strategy: "az-cli"
|
package/src/prompt.mjs
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { createInterface } from 'node:readline/promises';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Prompt the user for a destination directory.
|
|
6
|
-
* @param {string} defaultDest
|
|
7
|
-
* @returns {Promise<string>}
|
|
8
|
-
*/
|
|
9
|
-
export async function promptForDestination(defaultDest) {
|
|
10
|
-
const { stdin: input, stdout: output } = await import('node:process');
|
|
11
|
-
const rl = createInterface({ input, output });
|
|
12
|
-
|
|
13
|
-
rl.on('close', () => {});
|
|
14
|
-
|
|
15
|
-
let dest;
|
|
16
|
-
try {
|
|
17
|
-
const answer = await rl.question(
|
|
18
|
-
` Where should Kushi assets be installed? (${defaultDest}): `,
|
|
19
|
-
);
|
|
20
|
-
dest = answer.trim() || defaultDest;
|
|
21
|
-
} catch {
|
|
22
|
-
process.exit(0);
|
|
23
|
-
} finally {
|
|
24
|
-
rl.close();
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return validatePath(dest);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function validatePath(raw) {
|
|
31
|
-
if (path.isAbsolute(raw)) {
|
|
32
|
-
console.error('\n Please use a relative path (e.g., .kushi).\n');
|
|
33
|
-
process.exit(1);
|
|
34
|
-
}
|
|
35
|
-
const resolved = path.resolve(process.cwd(), raw);
|
|
36
|
-
const root = process.cwd();
|
|
37
|
-
if (!resolved.startsWith(root + path.sep) && resolved !== root) {
|
|
38
|
-
console.error('\n Destination must be within the current project.\n');
|
|
39
|
-
process.exit(1);
|
|
40
|
-
}
|
|
41
|
-
return raw.replace(/\\/g, '/').replace(/\/+$/, '');
|
|
42
|
-
}
|