kushi-agents 5.2.0 → 5.3.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,35 @@
1
+ {
2
+ "skill": "promote",
3
+ "cases": [
4
+ {
5
+ "id": "promote-refuses-on-customer-name",
6
+ "name": "kushi promote refuses without --force when a customer name is detected",
7
+ "input": "kushi promote acme answers/2026-05-27_confidence-ladder.md",
8
+ "expected_assertions": [
9
+ { "type": "contains", "value": "refused" },
10
+ { "type": "contains", "value": "identifier" }
11
+ ],
12
+ "grader_type": "script"
13
+ },
14
+ {
15
+ "id": "promote-force-writes-redacted",
16
+ "name": "kushi promote --force writes the redacted target + back-link + dual logs",
17
+ "input": "kushi promote acme answers/2026-05-27_confidence-ladder.md --force",
18
+ "expected_assertions": [
19
+ { "type": "contains", "value": "Promoted" },
20
+ { "type": "contains", "value": "redacts" }
21
+ ],
22
+ "grader_type": "script"
23
+ },
24
+ {
25
+ "id": "promote-clean-page-no-redactions",
26
+ "name": "kushi promote on a customer-free page writes immediately with zero redactions",
27
+ "input": "kushi promote acme answers/2026-05-27_generic-pattern.md",
28
+ "expected_assertions": [
29
+ { "type": "contains", "value": "Promoted" },
30
+ { "type": "contains", "value": "redacts: 0" }
31
+ ],
32
+ "grader_type": "script"
33
+ }
34
+ ]
35
+ }
@@ -76,6 +76,7 @@ Checks split into **core** (always run) and **deep** (opt-in).
76
76
  | D37.hooks | Hooks system (v5.2.0+) | `hooks.instructions.md` exists, `Invoke-Hooks.ps1` helper exists, hook-templates/ has ≥2 templates, any `hooks-log.md` uses canonical heading format. Sub-checks: `D37.hooks-doctrine-exists`, `D37.hooks-helper-exists`, `D37.hooks-templates-exist`, `D37.hooks-log-format`. |
77
77
  | D38.parallel | Parallel execution (v5.2.0+) | `parallel-execution.instructions.md` exists, `refresh-project/SKILL.md` references parallel dispatch, doctrine documents deterministic output ordering. Sub-checks: `D38.parallel-doctrine-exists`, `D38.refresh-supports-parallel`, `D38.deterministic-order`. |
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
+ | 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`. |
79
80
  | **CSC weekly-layout checks (kushi v4.9.0)** | | gated on `Resolve-EngagementRoots` — no-ops on the kushi repo itself. |
80
81
  | 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. |
81
82
  | 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. |
@@ -159,4 +160,6 @@ This skill is not invoked by `@Kushi <verb>` — it's a meta-skill. Triggers:
159
160
  - `instructions/hooks.instructions.md` (D37 validates hooks system)
160
161
  - `instructions/parallel-execution.instructions.md` (D38 validates parallel dispatch)
161
162
  - `instructions/otel.instructions.md` (D39 validates OTel export)
162
- - `instructions/schema-evolve.instructions.md` (D39 validates schema evolution)
163
+ - `instructions/schema-evolve.instructions.md` (D39 validates schema evolution)
164
+ - `instructions/global-wiki.instructions.md` (D40 validates global wiki shape + privacy)
165
+ - `instructions/multi-wiki-routing.instructions.md` (D40 validates routing doctrine presence)
@@ -2000,6 +2000,69 @@ process.stdout.write(JSON.stringify(out));
2000
2000
  }
2001
2001
  }
2002
2002
 
2003
+ # === D40.global-wiki — Global wiki + multi-wiki routing integrity (v5.3.0+) ===
2004
+
2005
+ # D40.global-wiki-doctrine-exists
2006
+ $globalWikiDoctrine = Join-Path $instructionsDir 'global-wiki.instructions.md'
2007
+ if (-not (Test-Path $globalWikiDoctrine)) {
2008
+ Add-Finding 'D40.global-wiki-doctrine-exists' 'GlobalWiki' 'warning' "global-wiki.instructions.md is missing" "Create plugin/instructions/global-wiki.instructions.md per v5.3.0 spec." $globalWikiDoctrine 0
2009
+ }
2010
+
2011
+ # D40.routing-doctrine-exists
2012
+ $routingDoctrine = Join-Path $instructionsDir 'multi-wiki-routing.instructions.md'
2013
+ if (-not (Test-Path $routingDoctrine)) {
2014
+ Add-Finding 'D40.routing-doctrine-exists' 'GlobalWiki' 'warning' "multi-wiki-routing.instructions.md is missing" "Create plugin/instructions/multi-wiki-routing.instructions.md per v5.3.0 spec." $routingDoctrine 0
2015
+ }
2016
+
2017
+ # D40.global-init-script-exists — implementation module + CLI dispatcher
2018
+ $globalModule = Join-Path $Root 'src\global-wiki.mjs'
2019
+ if (-not (Test-Path $globalModule)) {
2020
+ Add-Finding 'D40.global-init-script-exists' 'GlobalWiki' 'warning' "src/global-wiki.mjs is missing" "Create the global-wiki implementation module (resolveGlobalRoot/globalInit/globalStatus/globalAsk/globalLint)." $globalModule 0
2021
+ }
2022
+
2023
+ # D40.promote-script-exists — promote operation lives in the same module + CLI dispatcher
2024
+ $globalCli = Join-Path $Root 'src\global-wiki-cli.mjs'
2025
+ if (-not (Test-Path $globalCli)) {
2026
+ Add-Finding 'D40.promote-script-exists' 'GlobalWiki' 'warning' "src/global-wiki-cli.mjs (CLI dispatch for global + promote) is missing" "Create the CLI dispatcher that wires bin/cli.mjs to global-wiki.mjs (runGlobalInit/runPromote/etc)." $globalCli 0
2027
+ } elseif (Test-Path $globalModule) {
2028
+ $modContent = Get-Content -Raw $globalModule
2029
+ if ($modContent -notmatch 'export\s+function\s+promote\b') {
2030
+ Add-Finding 'D40.promote-script-exists' 'GlobalWiki' 'warning' "src/global-wiki.mjs does not export a promote() function" "Add the promote() export per multi-wiki-routing.instructions.md § Promote operation." $globalModule 0
2031
+ }
2032
+ }
2033
+
2034
+ # D40.global-wiki-shape — if a global root exists (env or default), verify scaffold
2035
+ $globalRoot = $env:KUSHI_GLOBAL_ROOT
2036
+ if (-not $globalRoot) {
2037
+ $userHome = if ($env:USERPROFILE) { $env:USERPROFILE } elseif ($env:HOME) { $env:HOME } else { $null }
2038
+ if ($userHome) { $globalRoot = Join-Path $userHome '.kushi-global' }
2039
+ }
2040
+ if ($globalRoot -and (Test-Path $globalRoot)) {
2041
+ $globalState = Join-Path $globalRoot 'State'
2042
+ if (-not (Test-Path $globalState)) {
2043
+ Add-Finding 'D40.global-wiki-shape' 'GlobalWiki' 'warning' "global wiki root exists but State/ is missing" "Run 'kushi global init' to scaffold the State/ tree." $globalState 0
2044
+ } else {
2045
+ foreach ($req in @('index.md', 'log.md')) {
2046
+ $f = Join-Path $globalState $req
2047
+ if (-not (Test-Path $f)) {
2048
+ Add-Finding 'D40.global-wiki-shape' 'GlobalWiki' 'warning' "global wiki State/ missing required file: $req" "Run 'kushi global init' to repair the scaffold." $f 0
2049
+ }
2050
+ }
2051
+ }
2052
+ }
2053
+
2054
+ # D40.no-customer-leak — scan State/*.md under the configured global root for potential-customer-leak callouts
2055
+ if ($globalRoot -and (Test-Path (Join-Path $globalRoot 'State'))) {
2056
+ $globalState = Join-Path $globalRoot 'State'
2057
+ $leakFiles = Get-ChildItem -Path $globalState -Recurse -Filter '*.md' -ErrorAction SilentlyContinue
2058
+ foreach ($lf in $leakFiles) {
2059
+ $leakLines = Select-String -Path $lf.FullName -Pattern '^\s*>\s*\[!warning\]\s+potential-customer-leak' -ErrorAction SilentlyContinue
2060
+ foreach ($ll in $leakLines) {
2061
+ Add-Finding 'D40.no-customer-leak' 'GlobalWiki' 'warning' "Open potential-customer-leak callout in global wiki" "Resolve the redaction in $($lf.Name) and remove the callout once the surrounding context is rewritten." $lf.FullName $ll.LineNumber
2062
+ }
2063
+ }
2064
+ }
2065
+
2003
2066
  # === Output ===
2004
2067
  if ($Targeted) {
2005
2068
  # Filter findings to those whose code, surface, file path, or message contain the substring.
@@ -35,6 +35,8 @@ Pure pedagogical skill. Explains kushi concepts, architecture decisions, and ope
35
35
  | graph, entities, links | `entity-graph.instructions.md` | `link-entities/SKILL.md` |
36
36
  | workiq, m365 | `workiq-only.instructions.md` | `workiq-input-sanitization.instructions.md` |
37
37
  | schema, conventions, remember | `schema-evolve.instructions.md` | `karpathy-state-layout.instructions.md` |
38
+ | global, global-wiki, cross-engagement | `global-wiki.instructions.md` | `multi-wiki-routing.instructions.md` |
39
+ | routing, --global, --project-only, promote | `multi-wiki-routing.instructions.md` | `global-wiki.instructions.md` |
38
40
  | install, setup, hosts | `multi-host-install.instructions.md` | `host-portability.instructions.md` |
39
41
  | evals, testing | `skill-evals.instructions.md` | `skill-authoring.instructions.md` |
40
42
 
@@ -32,6 +32,28 @@
32
32
  { "type": "contains", "value": "available" }
33
33
  ],
34
34
  "grader_type": "script"
35
+ },
36
+ {
37
+ "id": "teach-global-wiki",
38
+ "name": "Explain global wiki",
39
+ "input": "kushi explain global-wiki",
40
+ "expected_assertions": [
41
+ { "type": "contains", "value": "global" },
42
+ { "type": "contains", "value": "scope" },
43
+ { "type": "contains", "value": "promote" }
44
+ ],
45
+ "grader_type": "script"
46
+ },
47
+ {
48
+ "id": "teach-multi-wiki-routing",
49
+ "name": "Explain multi-wiki routing flags",
50
+ "input": "kushi explain routing",
51
+ "expected_assertions": [
52
+ { "type": "contains", "value": "--global" },
53
+ { "type": "contains", "value": "--project-only" },
54
+ { "type": "contains", "value": "project-first" }
55
+ ],
56
+ "grader_type": "script"
35
57
  }
36
58
  ]
37
59
  }
@@ -0,0 +1,158 @@
1
+ // kushi v5.3.0 — CLI dispatch helpers for global wiki + promote.
2
+ // These wrap the pure functions in global-wiki.mjs with console I/O,
3
+ // project-root resolution, and exit-code handling.
4
+
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import { globalInit, globalStatus, globalAsk, globalLint, promote, resolveGlobalRoot } from './global-wiki.mjs';
8
+
9
+ export async function runGlobalInit() {
10
+ const result = globalInit();
11
+ console.log('');
12
+ console.log(` Global wiki root: ${result.root}`);
13
+ console.log(` State dir : ${result.state}`);
14
+ if (result.created.length) {
15
+ console.log(` Created (${result.created.length}): ${result.created.join(', ')}`);
16
+ }
17
+ if (result.skipped.length) {
18
+ console.log(` Skipped (${result.skipped.length} already present)`);
19
+ }
20
+ console.log('');
21
+ console.log(" Next: 'kushi global status' or 'kushi promote <project> <page>'");
22
+ console.log('');
23
+ }
24
+
25
+ export async function runGlobalStatus() {
26
+ const status = globalStatus();
27
+ console.log('');
28
+ console.log(` Global wiki root: ${status.root}`);
29
+ if (!status.initialized) {
30
+ console.log(' Status: not initialized. Run `kushi global init`.');
31
+ console.log('');
32
+ return;
33
+ }
34
+ console.log(` Pages : ${status.counts.pages}`);
35
+ console.log(` Answers : ${status.counts.answers}`);
36
+ console.log(` Reports : ${status.counts.reports}`);
37
+ console.log(` Open review : ${status.counts.review_items}`);
38
+ console.log(` Newest mtime: ${status.newest_iso || '(none)'}`);
39
+ console.log('');
40
+ }
41
+
42
+ export async function runGlobalAsk(question) {
43
+ if (!question || !question.trim()) {
44
+ console.error('\n Usage: kushi global ask <question>\n');
45
+ process.exitCode = 1;
46
+ return;
47
+ }
48
+ const result = globalAsk({ question });
49
+ console.log('');
50
+ console.log(` Question: ${question}`);
51
+ console.log(` Result : ${result.message}`);
52
+ if (result.hits.length) {
53
+ console.log('');
54
+ console.log(' Hits:');
55
+ for (const h of result.hits) {
56
+ console.log(` ${h.provenance} ${h.name} (score=${h.score})`);
57
+ console.log(` file: ${h.file}`);
58
+ }
59
+ }
60
+ console.log('');
61
+ }
62
+
63
+ export async function runGlobalLint() {
64
+ const result = globalLint();
65
+ console.log('');
66
+ console.log(` Global wiki lint @ ${resolveGlobalRoot()}`);
67
+ if (!result.initialized) {
68
+ console.log(' Status: not initialized. Run `kushi global init`.');
69
+ console.log('');
70
+ return;
71
+ }
72
+ if (result.findings.length === 0) {
73
+ console.log(' ✅ No findings.');
74
+ console.log('');
75
+ return;
76
+ }
77
+ console.log(` ⚠️ ${result.findings.length} finding(s):`);
78
+ for (const f of result.findings) {
79
+ console.log(` [${f.class}] ${f.file}:${f.line} — ${f.snippet}`);
80
+ console.log(` fix: ${f.fix}`);
81
+ }
82
+ console.log('');
83
+ }
84
+
85
+ export async function runPromote(project, pagePath, { force = false } = {}) {
86
+ const projectRoot = resolveProjectRoot(project);
87
+ if (!projectRoot) {
88
+ console.error(`\n Could not resolve project '${project}' relative to ${process.cwd()}.\n`);
89
+ process.exitCode = 1;
90
+ return;
91
+ }
92
+ const fullSource = resolveSourcePage(projectRoot, pagePath);
93
+ if (!fullSource) {
94
+ console.error(`\n Could not find page '${pagePath}' under ${projectRoot}.\n`);
95
+ process.exitCode = 1;
96
+ return;
97
+ }
98
+
99
+ const result = promote({
100
+ project,
101
+ sourcePath: fullSource,
102
+ projectRoot,
103
+ force,
104
+ });
105
+
106
+ if (result.refused) {
107
+ console.error('');
108
+ console.error(` ❌ promote refused — ${result.detections.length} identifier hit(s):`);
109
+ for (const d of result.detections) {
110
+ console.error(` [${d.kind}] "${d.pattern}" × ${d.count} (lines: ${d.line_numbers.join(', ')})`);
111
+ }
112
+ console.error('');
113
+ console.error(' Review the hits, then re-run with --force to redact and write.');
114
+ console.error('');
115
+ process.exitCode = 2;
116
+ return;
117
+ }
118
+
119
+ console.log('');
120
+ console.log(' ✅ Promoted:');
121
+ console.log(` source : ${result.source}`);
122
+ console.log(` target : ${result.target}`);
123
+ console.log(` slug : ${result.slug}`);
124
+ console.log(` redacts: ${result.redactions.length} (${result.redactions.join(', ') || 'none'})`);
125
+ console.log(` at : ${result.promoted_at}`);
126
+ console.log('');
127
+ }
128
+
129
+ function resolveProjectRoot(project) {
130
+ const cwd = process.cwd();
131
+ const candidates = [
132
+ path.resolve(cwd, project),
133
+ path.resolve(cwd, '..', project),
134
+ ];
135
+ for (const c of candidates) {
136
+ if (fs.existsSync(c)) return c;
137
+ }
138
+ return null;
139
+ }
140
+
141
+ function resolveSourcePage(projectRoot, pagePath) {
142
+ // Allow either an absolute-from-project path or a State-relative path.
143
+ const direct = path.isAbsolute(pagePath) ? pagePath : path.join(projectRoot, pagePath);
144
+ if (fs.existsSync(direct)) return direct;
145
+
146
+ // Try to locate under Evidence/*/State/.
147
+ const evidence = path.join(projectRoot, 'Evidence');
148
+ if (fs.existsSync(evidence)) {
149
+ for (const alias of fs.readdirSync(evidence)) {
150
+ const cand = path.join(evidence, alias, 'State', pagePath);
151
+ if (fs.existsSync(cand)) return cand;
152
+ }
153
+ }
154
+ // Plain <project>/State/.
155
+ const plain = path.join(projectRoot, 'State', pagePath);
156
+ if (fs.existsSync(plain)) return plain;
157
+ return null;
158
+ }