@maintainabilityai/research-runner 0.1.34 → 0.1.36
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/dist/runner/skills.js +128 -0
- package/package.json +1 -1
package/dist/runner/skills.js
CHANGED
|
@@ -685,6 +685,130 @@ function makeSelfReviewHandler(persona) {
|
|
|
685
685
|
}
|
|
686
686
|
const handleSelfReviewArchitect = makeSelfReviewHandler('architect');
|
|
687
687
|
const handleSelfReviewSecurity = makeSelfReviewHandler('security');
|
|
688
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
689
|
+
// knowledge-prd — D-PR1.v1.1 fix. Was deployed as a Skill template but
|
|
690
|
+
// the runner had no handler, so the code-design-agent's first attempt at
|
|
691
|
+
// invoking it on PR #120 returned `{"ok":false,"reason":"unknown-skill"}`.
|
|
692
|
+
// Agent fell back to direct file read + grep, which worked, but the chain
|
|
693
|
+
// has no `knowledge-prd` event proving the PRD was structurally read.
|
|
694
|
+
//
|
|
695
|
+
// Parses `okrs/<id>/how/prd.md` for FR-NN + SR-NN entries with tolerant
|
|
696
|
+
// regex (mirrors B31's tolerance — accepts `FR-NN` / `FR NN` / `**FR-NN**`
|
|
697
|
+
// heading or bold markers). Best-effort extraction of cited sources +
|
|
698
|
+
// STRIDE / OWASP anchors per requirement.
|
|
699
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
700
|
+
const KnowledgePrdInput = zod_1.z.object({ okrId: zod_1.z.string().min(1) });
|
|
701
|
+
/**
|
|
702
|
+
* Extract FR-NN / SR-NN requirement entries from a PRD body. Tolerant
|
|
703
|
+
* to several markdown forms the prd-agent has emitted over time:
|
|
704
|
+
* - `### FR-01: <title>` (H3 heading)
|
|
705
|
+
* - `**FR-01**: <title>` (bold-anchor inline)
|
|
706
|
+
* - `- **FR-01**: <title>` (bullet w/ bold anchor)
|
|
707
|
+
*
|
|
708
|
+
* Returns one record per logical id. Same id seen twice (heading + bullet
|
|
709
|
+
* form) is deduped — first occurrence wins (heading usually).
|
|
710
|
+
*/
|
|
711
|
+
function parsePrdRequirements(body, prefix) {
|
|
712
|
+
const seen = new Set();
|
|
713
|
+
const out = [];
|
|
714
|
+
// Match the requirement id and the rest of the line. The id form
|
|
715
|
+
// accepts `FR-NN` / `FR NN` (no dash) for forgiveness — same shape as
|
|
716
|
+
// B31's `[CRSE]-?\d+`. Captures the text content that follows.
|
|
717
|
+
const idRegex = new RegExp(`(?:^|\\s|\\*\\*)${prefix}[-\\s]?(\\d+)(?:\\*\\*)?\\s*[:.]?\\s*(.*?)(?:\\*\\*|$)`, 'gmi');
|
|
718
|
+
const lines = body.split('\n');
|
|
719
|
+
// Walk line-by-line and accumulate a window of context (this line +
|
|
720
|
+
// next ~6 lines) so source/anchor citations on a "Traces to:" line
|
|
721
|
+
// immediately following the heading get associated with the right id.
|
|
722
|
+
for (let i = 0; i < lines.length; i++) {
|
|
723
|
+
const line = lines[i];
|
|
724
|
+
const m = line.match(new RegExp(`(?:^|\\s|\\*\\*)${prefix}[-\\s]?(\\d+)(?:\\*\\*)?\\s*[:.]\\s*(.*?)\\s*$`, 'i'));
|
|
725
|
+
if (!m) {
|
|
726
|
+
continue;
|
|
727
|
+
}
|
|
728
|
+
const num = m[1];
|
|
729
|
+
const id = `${prefix}-${num.padStart(2, '0')}`;
|
|
730
|
+
if (seen.has(id)) {
|
|
731
|
+
continue;
|
|
732
|
+
}
|
|
733
|
+
seen.add(id);
|
|
734
|
+
let text = (m[2] || '').replace(/\*\*/g, '').trim();
|
|
735
|
+
// Collect 6 lines forward for source / anchor scanning.
|
|
736
|
+
const window = lines.slice(i, Math.min(i + 7, lines.length)).join('\n');
|
|
737
|
+
const sources = [...window.matchAll(/[CRSE]-?\d+/g)].map(x => x[0].replace(/(?<=[CRSE])\B/, '-').replace('--', '-'));
|
|
738
|
+
const dedupSrc = Array.from(new Set(sources));
|
|
739
|
+
const record = { id, text };
|
|
740
|
+
if (prefix === 'FR' && dedupSrc.length > 0) {
|
|
741
|
+
record.sources = dedupSrc;
|
|
742
|
+
}
|
|
743
|
+
if (prefix === 'SR') {
|
|
744
|
+
const stride = [...window.matchAll(/THR-\d{3}/gi)].map(x => x[0].toUpperCase());
|
|
745
|
+
const owasp = [...window.matchAll(/A0[1-9]|A10/gi)].map(x => x[0].toUpperCase());
|
|
746
|
+
if (stride.length > 0) {
|
|
747
|
+
record.stride = Array.from(new Set(stride));
|
|
748
|
+
}
|
|
749
|
+
if (owasp.length > 0) {
|
|
750
|
+
record.owasp = Array.from(new Set(owasp));
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
out.push(record);
|
|
754
|
+
}
|
|
755
|
+
void idRegex;
|
|
756
|
+
return out;
|
|
757
|
+
}
|
|
758
|
+
/**
|
|
759
|
+
* Extract a Coverage Analysis table from the PRD body. Format expected:
|
|
760
|
+
* | FR/SR | Source | Status |
|
|
761
|
+
* |---|---|---|
|
|
762
|
+
* | FR-01 | R-2,E-1 | YES |
|
|
763
|
+
* ...
|
|
764
|
+
* Returns a map id → bool (YES → true, PARTIAL/NO → false).
|
|
765
|
+
*/
|
|
766
|
+
function parsePrdCoverage(body) {
|
|
767
|
+
const coverage = {};
|
|
768
|
+
const lines = body.split('\n');
|
|
769
|
+
for (const line of lines) {
|
|
770
|
+
const m = line.match(/^\s*\|\s*((?:FR|SR)[-\s]?\d+)\s*\|.*\|\s*(YES|PARTIAL|NO)\s*\|/i);
|
|
771
|
+
if (!m) {
|
|
772
|
+
continue;
|
|
773
|
+
}
|
|
774
|
+
const rawId = m[1].toUpperCase();
|
|
775
|
+
const numMatch = rawId.match(/(\d+)/);
|
|
776
|
+
if (!numMatch) {
|
|
777
|
+
continue;
|
|
778
|
+
}
|
|
779
|
+
const id = `${rawId.startsWith('FR') ? 'FR' : 'SR'}-${numMatch[1].padStart(2, '0')}`;
|
|
780
|
+
coverage[id] = m[2].toUpperCase() === 'YES';
|
|
781
|
+
}
|
|
782
|
+
return coverage;
|
|
783
|
+
}
|
|
784
|
+
const handleKnowledgePrd = async (input) => {
|
|
785
|
+
const parsed = KnowledgePrdInput.safeParse(input);
|
|
786
|
+
if (!parsed.success) {
|
|
787
|
+
return { ok: false, reason: `bad-input: ${parsed.error.message}` };
|
|
788
|
+
}
|
|
789
|
+
const docPath = path.join(meshPath(), 'okrs', parsed.data.okrId, 'how', 'prd.md');
|
|
790
|
+
if (!fs.existsSync(docPath)) {
|
|
791
|
+
return { ok: false, reason: 'prd-not-merged-yet' };
|
|
792
|
+
}
|
|
793
|
+
const body = fs.readFileSync(docPath, 'utf8');
|
|
794
|
+
const functionalRequirements = parsePrdRequirements(body, 'FR');
|
|
795
|
+
const securityRequirements = parsePrdRequirements(body, 'SR');
|
|
796
|
+
const coverage = parsePrdCoverage(body);
|
|
797
|
+
const auditMetadata = {
|
|
798
|
+
okr_id: parsed.data.okrId,
|
|
799
|
+
fr_count: functionalRequirements.length,
|
|
800
|
+
sr_count: securityRequirements.length,
|
|
801
|
+
coverage_rows: Object.keys(coverage).length,
|
|
802
|
+
};
|
|
803
|
+
return {
|
|
804
|
+
ok: true,
|
|
805
|
+
functionalRequirements,
|
|
806
|
+
securityRequirements,
|
|
807
|
+
coverage,
|
|
808
|
+
docPath,
|
|
809
|
+
auditMetadata,
|
|
810
|
+
};
|
|
811
|
+
};
|
|
688
812
|
/**
|
|
689
813
|
* D-PR1 — code-phase persona-switch self-review. Same B29 pattern as the
|
|
690
814
|
* PRD-phase architect/security handlers above, but reads the WHAT-phase
|
|
@@ -1623,6 +1747,10 @@ exports.SKILLS = {
|
|
|
1623
1747
|
'knowledge-mesh-threats': handleKnowledgeMeshThreats,
|
|
1624
1748
|
'knowledge-mesh-adrs': handleKnowledgeMeshAdrs,
|
|
1625
1749
|
'knowledge-research': handleKnowledgeResearch,
|
|
1750
|
+
// D-PR1.v1.1 — knowledge-prd handler (SKILL.md was deployed but no
|
|
1751
|
+
// runner backend existed, causing the code-design-agent to fall back
|
|
1752
|
+
// to direct file read with no chain evidence on PR #120).
|
|
1753
|
+
'knowledge-prd': handleKnowledgePrd,
|
|
1626
1754
|
'context-architecture': handleContextArchitecture,
|
|
1627
1755
|
'context-security': handleContextSecurity,
|
|
1628
1756
|
'context-quality': handleContextQuality,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maintainabilityai/research-runner",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.36",
|
|
4
4
|
"description": "Research + PRD agent runner — orchestrates the Archeologist and PRD pipelines for the MaintainabilityAI governance mesh",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "MaintainabilityAI",
|