supipowers 2.0.2 → 2.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/README.md +5 -6
- package/package.json +4 -2
- package/skills/harness/SKILL.md +1 -0
- package/src/bootstrap.ts +8 -133
- package/src/commands/optimize-context.ts +153 -16
- package/src/commands/runbook.ts +511 -0
- package/src/config/defaults.ts +5 -5
- package/src/config/loader.ts +1 -0
- package/src/config/schema.ts +2 -6
- package/src/context/rule-renderer.ts +274 -2
- package/src/context/runbook-extension-template.ts +193 -0
- package/src/context/startup-check.ts +197 -2
- package/src/context/startup-optimizer.ts +133 -10
- package/src/context-mode/knowledge/store.ts +381 -43
- package/src/context-mode/tools.ts +41 -3
- package/src/deps/registry.ts +1 -12
- package/src/fix-pr/assessment.ts +1 -0
- package/src/fix-pr/prompt-builder.ts +1 -0
- package/src/git/commit.ts +76 -18
- package/src/harness/command.ts +201 -12
- package/src/harness/default-agents/docs.md +39 -0
- package/src/harness/docs/config.ts +29 -0
- package/src/harness/docs/glob-match.ts +27 -0
- package/src/harness/docs/index-renderer.ts +82 -0
- package/src/harness/docs/provenance.ts +125 -0
- package/src/harness/docs/regen-decision.ts +167 -0
- package/src/harness/docs/representative-files.ts +175 -0
- package/src/harness/docs/source-hash.ts +106 -0
- package/src/harness/docs/validator.ts +233 -0
- package/src/harness/git-verification.ts +515 -0
- package/src/harness/git-verify-qa.ts +406 -0
- package/src/harness/hooks/layer-context-inject.ts +35 -1
- package/src/harness/hooks/register.ts +24 -3
- package/src/harness/pipeline.ts +37 -13
- package/src/harness/pr-comment/baseline.ts +105 -0
- package/src/harness/pr-comment/ci-env.ts +120 -0
- package/src/harness/pr-comment/gh-poster.ts +227 -0
- package/src/harness/pr-comment/handler.ts +198 -0
- package/src/harness/pr-comment/render.ts +297 -0
- package/src/harness/pr-comment/status.ts +95 -0
- package/src/harness/pr-comment/types.ts +73 -0
- package/src/harness/pr-comment/workflow-summary.ts +47 -0
- package/src/harness/project-paths.ts +95 -0
- package/src/harness/stages/design.ts +1 -0
- package/src/harness/stages/discover.ts +1 -13
- package/src/harness/stages/docs.ts +708 -0
- package/src/harness/stages/implement-apply.ts +934 -0
- package/src/harness/stages/implement.ts +64 -51
- package/src/harness/stages/plan.ts +25 -16
- package/src/harness/stages/validate.ts +478 -0
- package/src/harness/storage.ts +142 -0
- package/src/harness/tools.ts +130 -0
- package/src/mempalace/bridge.ts +207 -41
- package/src/mempalace/config.ts +10 -4
- package/src/mempalace/format.ts +122 -6
- package/src/mempalace/hooks.ts +204 -56
- package/src/mempalace/installer-helper.ts +18 -4
- package/src/mempalace/python/mempalace_bridge.py +128 -3
- package/src/mempalace/runtime.ts +53 -16
- package/src/mempalace/schema.ts +151 -30
- package/src/mempalace/session-summary.ts +5 -0
- package/src/mempalace/tool.ts +17 -4
- package/src/mempalace/upstream-limits.ts +69 -0
- package/src/planning/approval-flow.ts +25 -2
- package/src/planning/planning-ask-tool.ts +34 -4
- package/src/planning/system-prompt.ts +1 -1
- package/src/tool-catalog/active-tool-controller.ts +0 -22
- package/src/tool-catalog/active-tool-planner.ts +0 -26
- package/src/tool-catalog/tool-groups.ts +1 -9
- package/src/types.ts +127 -8
- package/src/ui-design/session.ts +114 -8
- package/src/utils/executable.ts +10 -1
- package/src/workspace/state-paths.ts +1 -1
- package/src/commands/mcp.ts +0 -814
- package/src/mcp/activation.ts +0 -77
- package/src/mcp/config.ts +0 -223
- package/src/mcp/docs.ts +0 -154
- package/src/mcp/gateway.ts +0 -103
- package/src/mcp/lifecycle.ts +0 -79
- package/src/mcp/manager-tool.ts +0 -104
- package/src/mcp/mcpc.ts +0 -113
- package/src/mcp/registry.ts +0 -98
- package/src/mcp/triggers.ts +0 -62
- package/src/mcp/types.ts +0 -95
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Synchronous validator for a per-layer doc rendered by a docs-stage subagent.
|
|
3
|
+
*
|
|
4
|
+
* Validation is mechanical: LOC caps, required headings in order, frontmatter shape,
|
|
5
|
+
* agent-context section cap, sourceHash match, no TODO/XXX markers. The validator runs
|
|
6
|
+
* inside the `harness_docs_record` tool handler, so failures must be safe to surface to
|
|
7
|
+
* the subagent (structured error strings).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { parseProvenance } from "./provenance.js";
|
|
11
|
+
|
|
12
|
+
export interface ValidateLayerDocOptions {
|
|
13
|
+
/** Expected layer id (from the assignment). */
|
|
14
|
+
expectedLayerId: string;
|
|
15
|
+
/** Expected sourceHash; the renderer must embed this verbatim. */
|
|
16
|
+
expectedSourceHash: string;
|
|
17
|
+
/** Hard cap on total LOC (default 150). */
|
|
18
|
+
maxDocLoc?: number;
|
|
19
|
+
/** Hard cap on the `## Agent context` section LOC (default 30). */
|
|
20
|
+
maxAgentContextLoc?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const DEFAULT_MAX_DOC_LOC = 150;
|
|
24
|
+
export const DEFAULT_MAX_AGENT_CONTEXT_LOC = 30;
|
|
25
|
+
|
|
26
|
+
/** Required headings, in the order the doc must place them. */
|
|
27
|
+
export const REQUIRED_HEADINGS: readonly string[] = [
|
|
28
|
+
"## Agent context",
|
|
29
|
+
"## Purpose",
|
|
30
|
+
"## Files",
|
|
31
|
+
"## Imports",
|
|
32
|
+
"## Conventions",
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
const PLACEHOLDER_PATTERN = /\b(TODO|XXX|FIXME|TBD|<placeholder>)\b/;
|
|
36
|
+
|
|
37
|
+
export interface ValidateLayerDocResult {
|
|
38
|
+
ok: boolean;
|
|
39
|
+
errors: string[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Validate a layer-doc markdown body. Returns `{ ok, errors }`. `errors` is empty when
|
|
44
|
+
* `ok === true`.
|
|
45
|
+
*/
|
|
46
|
+
export function validateLayerDocMarkdown(
|
|
47
|
+
markdown: string,
|
|
48
|
+
options: ValidateLayerDocOptions,
|
|
49
|
+
): ValidateLayerDocResult {
|
|
50
|
+
const errors: string[] = [];
|
|
51
|
+
const maxDocLoc = options.maxDocLoc ?? DEFAULT_MAX_DOC_LOC;
|
|
52
|
+
const maxAgentContextLoc = options.maxAgentContextLoc ?? DEFAULT_MAX_AGENT_CONTEXT_LOC;
|
|
53
|
+
|
|
54
|
+
// 1. Provenance marker on first line.
|
|
55
|
+
const parsed = parseProvenance(markdown);
|
|
56
|
+
if (!parsed) {
|
|
57
|
+
errors.push("missing or malformed provenance marker on the first line");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 2. LOC budget.
|
|
61
|
+
const lineCount = countDocLines(markdown);
|
|
62
|
+
if (lineCount > maxDocLoc) {
|
|
63
|
+
errors.push(`doc has ${lineCount} LOC; max is ${maxDocLoc}`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 3. Frontmatter — between the first --- after the marker and the next ---.
|
|
67
|
+
const frontmatter = extractFrontmatter(parsed?.body ?? markdown);
|
|
68
|
+
if (!frontmatter) {
|
|
69
|
+
errors.push("missing YAML frontmatter (---\\n…\\n---) immediately after the marker");
|
|
70
|
+
} else {
|
|
71
|
+
if (frontmatter.layer !== options.expectedLayerId) {
|
|
72
|
+
errors.push(
|
|
73
|
+
`frontmatter layer mismatch (got "${frontmatter.layer ?? ""}", expected "${options.expectedLayerId}")`,
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
if (!frontmatter.generatedAt) {
|
|
77
|
+
errors.push("frontmatter is missing `generatedAt`");
|
|
78
|
+
}
|
|
79
|
+
if (!frontmatter.sourceHash) {
|
|
80
|
+
errors.push("frontmatter is missing `sourceHash`");
|
|
81
|
+
} else if (frontmatter.sourceHash !== options.expectedSourceHash) {
|
|
82
|
+
errors.push(
|
|
83
|
+
`frontmatter sourceHash mismatch (got "${frontmatter.sourceHash}", expected "${options.expectedSourceHash}")`,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 4. Required headings in order.
|
|
89
|
+
const headingResult = checkHeadings(markdown);
|
|
90
|
+
if (headingResult.missing.length > 0) {
|
|
91
|
+
errors.push(`missing required heading(s): ${headingResult.missing.join(", ")}`);
|
|
92
|
+
}
|
|
93
|
+
if (headingResult.outOfOrder) {
|
|
94
|
+
errors.push(`required headings appear out of order — expected: ${REQUIRED_HEADINGS.join(" → ")}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// 5. Agent-context cap.
|
|
98
|
+
const agentSectionLoc = sectionLoc(markdown, "## Agent context");
|
|
99
|
+
if (agentSectionLoc > maxAgentContextLoc) {
|
|
100
|
+
errors.push(`## Agent context section is ${agentSectionLoc} LOC; max is ${maxAgentContextLoc}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 6. Placeholder markers.
|
|
104
|
+
if (PLACEHOLDER_PATTERN.test(markdown)) {
|
|
105
|
+
errors.push("doc contains a TODO/XXX/FIXME/TBD placeholder marker; remove before recording");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return { ok: errors.length === 0, errors };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Count LOC ignoring a trailing empty line (matches representativeFiles `countLines`). */
|
|
112
|
+
function countDocLines(markdown: string): number {
|
|
113
|
+
if (markdown.length === 0) return 0;
|
|
114
|
+
let count = 1;
|
|
115
|
+
for (let i = 0; i < markdown.length; i += 1) {
|
|
116
|
+
if (markdown.charCodeAt(i) === 10 /* \n */) count += 1;
|
|
117
|
+
}
|
|
118
|
+
if (markdown.charCodeAt(markdown.length - 1) === 10) count -= 1;
|
|
119
|
+
return count;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
interface ParsedFrontmatter {
|
|
123
|
+
layer?: string;
|
|
124
|
+
generatedAt?: string;
|
|
125
|
+
sourceHash?: string;
|
|
126
|
+
/** Raw map for tests / future fields. */
|
|
127
|
+
raw: Map<string, string>;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function extractFrontmatter(body: string): ParsedFrontmatter | null {
|
|
131
|
+
if (!body.startsWith("---")) return null;
|
|
132
|
+
const newlineAfterOpen = body.indexOf("\n");
|
|
133
|
+
if (newlineAfterOpen < 0) return null;
|
|
134
|
+
// Find the next "\n---" closer.
|
|
135
|
+
const closeIdx = body.indexOf("\n---", newlineAfterOpen);
|
|
136
|
+
if (closeIdx < 0) return null;
|
|
137
|
+
|
|
138
|
+
const inner = body.slice(newlineAfterOpen + 1, closeIdx);
|
|
139
|
+
const map = new Map<string, string>();
|
|
140
|
+
for (const line of inner.split("\n")) {
|
|
141
|
+
const m = line.match(/^([A-Za-z][A-Za-z0-9_-]*)\s*:\s*(.*)$/);
|
|
142
|
+
if (!m) continue;
|
|
143
|
+
map.set(m[1], m[2].trim());
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
layer: map.get("layer"),
|
|
147
|
+
generatedAt: map.get("generatedAt"),
|
|
148
|
+
sourceHash: map.get("sourceHash"),
|
|
149
|
+
raw: map,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function checkHeadings(markdown: string): { missing: string[]; outOfOrder: boolean } {
|
|
154
|
+
const seen: { heading: string; index: number }[] = [];
|
|
155
|
+
for (const heading of REQUIRED_HEADINGS) {
|
|
156
|
+
const pattern = new RegExp(`^${escapeRegex(heading)}\\s*$`, "m");
|
|
157
|
+
const match = markdown.match(pattern);
|
|
158
|
+
if (match && typeof match.index === "number") {
|
|
159
|
+
seen.push({ heading, index: match.index });
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const missing = REQUIRED_HEADINGS.filter(
|
|
164
|
+
(h) => !seen.some((entry) => entry.heading === h),
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
// Check ordering only for headings we did see.
|
|
168
|
+
let outOfOrder = false;
|
|
169
|
+
let lastIndex = -1;
|
|
170
|
+
for (const heading of REQUIRED_HEADINGS) {
|
|
171
|
+
const entry = seen.find((s) => s.heading === heading);
|
|
172
|
+
if (!entry) continue;
|
|
173
|
+
if (entry.index < lastIndex) {
|
|
174
|
+
outOfOrder = true;
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
lastIndex = entry.index;
|
|
178
|
+
}
|
|
179
|
+
return { missing: missing.slice(), outOfOrder };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Count LOC of the section starting at `heading` (exclusive) up to the next `## ` heading. */
|
|
183
|
+
export function sectionLoc(markdown: string, heading: string): number {
|
|
184
|
+
const lines = markdown.split("\n");
|
|
185
|
+
let start = -1;
|
|
186
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
187
|
+
if (lines[i] === heading || lines[i].startsWith(`${heading} `)) {
|
|
188
|
+
start = i;
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (start < 0) return 0;
|
|
193
|
+
let end = lines.length;
|
|
194
|
+
for (let i = start + 1; i < lines.length; i += 1) {
|
|
195
|
+
if (lines[i].startsWith("## ") || lines[i] === "##") {
|
|
196
|
+
end = i;
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
// Trim trailing blank lines.
|
|
201
|
+
while (end > start + 1 && lines[end - 1].trim() === "") end -= 1;
|
|
202
|
+
return Math.max(0, end - start - 1);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** Extract the body of the `## Agent context` section. Returns "" when missing. */
|
|
206
|
+
export function extractAgentContextSection(markdown: string, maxLoc?: number): string {
|
|
207
|
+
const lines = markdown.split("\n");
|
|
208
|
+
let start = -1;
|
|
209
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
210
|
+
if (lines[i] === "## Agent context" || lines[i].startsWith("## Agent context ")) {
|
|
211
|
+
start = i;
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (start < 0) return "";
|
|
216
|
+
let end = lines.length;
|
|
217
|
+
for (let i = start + 1; i < lines.length; i += 1) {
|
|
218
|
+
if (lines[i].startsWith("## ") || lines[i] === "##") {
|
|
219
|
+
end = i;
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
while (end > start + 1 && lines[end - 1].trim() === "") end -= 1;
|
|
224
|
+
let bodyLines = lines.slice(start + 1, end);
|
|
225
|
+
if (maxLoc !== undefined && bodyLines.length > maxLoc) {
|
|
226
|
+
bodyLines = bodyLines.slice(0, maxLoc);
|
|
227
|
+
}
|
|
228
|
+
return bodyLines.join("\n");
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function escapeRegex(input: string): string {
|
|
232
|
+
return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
233
|
+
}
|