fix(ms-ai-architect): RX-P2 reg-kjede-wiring + komprehensiv agent-sti-forankring [skip-docs]
- classify->dpia->ros kjede-wiring: dpia/ros Task-templater far eksplisitte AI Act-klassifiserings- + DPIA-funn-felt (kjede-data agentene allerede branchet pa) - dpia-agent uutforbar "spor bruker" -> "marker vurderingen" (Hvis-ikke-klassifisert + Error Handling); speilet til ros-analysis-agent - adr-writer-agent: Write-verktoy fjernet, returnerer ADR-markdown til hovedkontekst (command er eneste skriver -- subagenter skriver aldri) - KB-sti-forankring (komprehensiv): 36 bare referanse-subdir-stier i 6 agenter + 2 commands fullkvalifisert med CLAUDE_PLUGIN_ROOT/skills/<skill>/references/ --- ogsa innen-skill kortformer, uresolverbare i installert modus - mekanisme: validate-plugin.sh Check 6d (bare-subdir-lint m/ hyphen-guard) + negativ-probe-test (beviser tenner) + RX-P2 wiring-regresjonstester Suite 859/859 exit 0. validate-plugin 250 PASS / 0 FAIL. 145 forankrede stier verifisert eksisterende (0 mangler).
This commit is contained in:
parent
5b05009b44
commit
b68514487c
13 changed files with 151 additions and 36 deletions
45
tests/kb-update/test-rx-p2-wiring.test.mjs
Normal file
45
tests/kb-update/test-rx-p2-wiring.test.mjs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
// RX-P2 regression guards: classify→dpia→ros chain wiring (del 1), removal of the
|
||||
// unexecutable "ask user" subagent instructions (del 2), and single ADR write-ownership
|
||||
// (del 4). Path anchoring (del 3) + its lint (del 5) are enforced by validate-plugin.sh
|
||||
// Check 6d via test-validate-plugin.test.mjs.
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname, join } from 'node:path';
|
||||
|
||||
const here = dirname(fileURLToPath(import.meta.url));
|
||||
const root = join(here, '..', '..'); // tests/kb-update -> plugin root
|
||||
const read = (rel) => readFileSync(join(root, rel), 'utf8');
|
||||
|
||||
// --- Del 1: chain wiring — the Task templates must carry explicit chain-data fields ---
|
||||
test('dpia.md Task template passes AI Act classification as chain-data', () => {
|
||||
const dpia = read('commands/dpia.md');
|
||||
assert.match(dpia, /\*\*AI Act-klassifisering \(fra \/architect:classify/);
|
||||
});
|
||||
|
||||
test('ros.md Task template passes AI Act classification (dim. 6) + DPIA findings as chain-data', () => {
|
||||
const ros = read('commands/ros.md');
|
||||
assert.match(ros, /\*\*AI Act-klassifisering \(dimensjon 6/);
|
||||
assert.match(ros, /\*\*DPIA-funn \(fra \/architect:dpia/);
|
||||
});
|
||||
|
||||
// --- Del 2: no unexecutable "ask the user" instructions in Task-spawned subagents ---
|
||||
test('dpia-agent.md no longer instructs the subagent to ask the user', () => {
|
||||
const agent = read('agents/dpia-agent.md');
|
||||
assert.doesNotMatch(agent, /Spør om det bør gjøres/);
|
||||
assert.doesNotMatch(agent, /Request specific details needed/);
|
||||
});
|
||||
|
||||
test('ros-analysis-agent.md no longer instructs the subagent to ask the user', () => {
|
||||
const agent = read('agents/ros-analysis-agent.md');
|
||||
assert.doesNotMatch(agent, /Request specific details needed/);
|
||||
});
|
||||
|
||||
// --- Del 4: adr-writer-agent returns content; the command is the single writer ---
|
||||
test('adr-writer-agent.md does not carry the Write tool and does not write files', () => {
|
||||
const agent = read('agents/adr-writer-agent.md');
|
||||
const toolsLine = agent.split('\n').find((l) => l.startsWith('tools:')) || '';
|
||||
assert.doesNotMatch(toolsLine, /Write/, 'adr-writer-agent must not have the Write tool (subagents never write)');
|
||||
assert.doesNotMatch(agent, /### 4\. Write to File/);
|
||||
});
|
||||
|
|
@ -6,6 +6,7 @@ import assert from 'node:assert/strict';
|
|||
import { execFileSync } from 'node:child_process';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { writeFileSync, rmSync } from 'node:fs';
|
||||
|
||||
const here = dirname(fileURLToPath(import.meta.url));
|
||||
const pluginRoot = join(here, '..', '..'); // tests/kb-update -> plugin root
|
||||
|
|
@ -23,3 +24,42 @@ test('validate-plugin.sh passes (frontmatter, encoding, KB refs, plugin.json, in
|
|||
assert.match(out, /VALIDATION PASSED/);
|
||||
assert.doesNotMatch(out, /VALIDATION FAILED/);
|
||||
});
|
||||
|
||||
// RX-P2: prove Check 6d has teeth — an unanchored reference-subdir path must be caught.
|
||||
// Without a negative probe, the positive test above only proves the plugin is currently
|
||||
// clean, not that 6d actually fires.
|
||||
test('validate-plugin.sh Check 6d catches an unanchored reference-subdir path (negative probe)', () => {
|
||||
const probe = join(pluginRoot, 'agents', '__rx_p2_6d_probe__.md');
|
||||
writeFileSync(
|
||||
probe,
|
||||
[
|
||||
'---',
|
||||
'name: rx-p2-6d-probe',
|
||||
'description: temporary probe for the 6d negative test',
|
||||
'model: opus',
|
||||
'color: blue',
|
||||
'tools: ["Read"]',
|
||||
'---',
|
||||
'',
|
||||
'# Probe',
|
||||
'',
|
||||
'Load: `responsible-ai/ai-act-compliance-guide.md`',
|
||||
'',
|
||||
].join('\n'),
|
||||
);
|
||||
try {
|
||||
let failed = false;
|
||||
let out = '';
|
||||
try {
|
||||
out = execFileSync('bash', [script], { encoding: 'utf8', cwd: pluginRoot });
|
||||
} catch (err) {
|
||||
failed = true;
|
||||
out = `${err.stdout || ''}${err.stderr || ''}`;
|
||||
}
|
||||
assert.ok(failed, 'validate-plugin.sh must exit non-zero when a bare reference-subdir path is present');
|
||||
assert.match(out, /bare reference-subdir path/);
|
||||
assert.match(out, /__rx_p2_6d_probe__\.md/);
|
||||
} finally {
|
||||
rmSync(probe, { force: true });
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -349,6 +349,33 @@ else
|
|||
pass "No sonnet/haiku pinned in Task delegations"
|
||||
fi
|
||||
|
||||
# 6d: every bundled reference-subdir path must be ${CLAUDE_PLUGIN_ROOT}-anchored.
|
||||
# Short-forms like `responsible-ai/foo.md` (a reference subdir + file, without the
|
||||
# full skills/<skill>/references/ prefix) resolve only in dev cwd -- installed mode
|
||||
# breaks. A path is anchored iff the subdir is immediately preceded by '/'
|
||||
# (.../references/<subdir>/). The (^|[^A-Za-z0-9._/-]) guard fires on bare short-forms
|
||||
# (preceded by backtick/space/comma/colon) while skipping anchored paths (preceded
|
||||
# by '/') and compound subdir names (e.g. 'architecture' inside 'rag-architecture',
|
||||
# preceded by '-').
|
||||
REF_SUBDIRS='architecture|copilot-extensibility|development|platforms|prompt-engineering|agent-orchestration|api-management|azure-ai-services|data-engineering|mlops-genaiops|multi-modal|rag-architecture|monitoring-observability|norwegian-public-sector-governance|responsible-ai|bcdr|hybrid-edge|ai-security-engineering|cost-optimization|performance-scalability'
|
||||
subdir_offenders=0
|
||||
for f in "$PLUGIN_ROOT"/commands/*.md "$PLUGIN_ROOT"/agents/*.md; do
|
||||
[ -f "$f" ] || continue
|
||||
rel="${f#$PLUGIN_ROOT/}"
|
||||
bare="$(grep -nE "(^|[^A-Za-z0-9._/-])($REF_SUBDIRS)/[A-Za-z0-9._-]+\.md" "$f" || true)"
|
||||
if [ -n "$bare" ]; then
|
||||
while IFS= read -r bline; do
|
||||
[ -n "$bline" ] || continue
|
||||
lineno="${bline%%:*}"
|
||||
fail "$rel:$lineno: bare reference-subdir path (needs \${CLAUDE_PLUGIN_ROOT}/skills/<skill>/references/ prefix for installed mode)"
|
||||
subdir_offenders=$((subdir_offenders + 1))
|
||||
done <<< "$bare"
|
||||
fi
|
||||
done
|
||||
if [ "$subdir_offenders" -eq 0 ]; then
|
||||
pass "All reference-subdir paths are \${CLAUDE_PLUGIN_ROOT}-anchored"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# -------------------------------------------------------
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue