supipowers 2.0.1 → 2.1.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.
Files changed (80) hide show
  1. package/README.md +10 -6
  2. package/package.json +4 -2
  3. package/skills/harness/SKILL.md +1 -0
  4. package/src/bootstrap.ts +5 -133
  5. package/src/commands/clear.ts +6 -6
  6. package/src/commands/release.ts +3 -1
  7. package/src/commands/update.ts +1 -1
  8. package/src/config/defaults.ts +5 -5
  9. package/src/config/loader.ts +1 -0
  10. package/src/config/schema.ts +2 -6
  11. package/src/context/analyzer.ts +104 -35
  12. package/src/context-mode/knowledge/store.ts +381 -43
  13. package/src/context-mode/tools.ts +41 -3
  14. package/src/deps/registry.ts +1 -12
  15. package/src/fix-pr/assessment.ts +1 -0
  16. package/src/fix-pr/prompt-builder.ts +1 -0
  17. package/src/git/commit.ts +76 -18
  18. package/src/harness/command.ts +103 -6
  19. package/src/harness/default-agents/docs.md +39 -0
  20. package/src/harness/docs/config.ts +29 -0
  21. package/src/harness/docs/glob-match.ts +27 -0
  22. package/src/harness/docs/index-renderer.ts +82 -0
  23. package/src/harness/docs/provenance.ts +125 -0
  24. package/src/harness/docs/regen-decision.ts +167 -0
  25. package/src/harness/docs/representative-files.ts +175 -0
  26. package/src/harness/docs/source-hash.ts +106 -0
  27. package/src/harness/docs/validator.ts +233 -0
  28. package/src/harness/hooks/layer-context-inject.ts +35 -1
  29. package/src/harness/hooks/register.ts +24 -3
  30. package/src/harness/pipeline.ts +20 -5
  31. package/src/harness/pr-comment/baseline.ts +105 -0
  32. package/src/harness/pr-comment/ci-env.ts +120 -0
  33. package/src/harness/pr-comment/gh-poster.ts +227 -0
  34. package/src/harness/pr-comment/handler.ts +198 -0
  35. package/src/harness/pr-comment/render.ts +297 -0
  36. package/src/harness/pr-comment/status.ts +95 -0
  37. package/src/harness/pr-comment/types.ts +73 -0
  38. package/src/harness/pr-comment/workflow-summary.ts +47 -0
  39. package/src/harness/project-paths.ts +95 -0
  40. package/src/harness/stages/design.ts +1 -0
  41. package/src/harness/stages/discover.ts +1 -13
  42. package/src/harness/stages/docs.ts +708 -0
  43. package/src/harness/stages/implement-apply.ts +877 -0
  44. package/src/harness/stages/implement.ts +64 -51
  45. package/src/harness/stages/plan.ts +25 -16
  46. package/src/harness/stages/validate.ts +370 -0
  47. package/src/harness/storage.ts +142 -0
  48. package/src/harness/tools.ts +130 -0
  49. package/src/mempalace/bridge.ts +207 -41
  50. package/src/mempalace/config.ts +10 -4
  51. package/src/mempalace/format.ts +122 -6
  52. package/src/mempalace/hooks.ts +204 -56
  53. package/src/mempalace/installer-helper.ts +18 -4
  54. package/src/mempalace/python/mempalace_bridge.py +128 -3
  55. package/src/mempalace/runtime.ts +55 -18
  56. package/src/mempalace/schema.ts +151 -30
  57. package/src/mempalace/session-summary.ts +5 -0
  58. package/src/mempalace/tool.ts +17 -4
  59. package/src/mempalace/upstream-limits.ts +69 -0
  60. package/src/planning/approval-flow.ts +25 -2
  61. package/src/planning/planning-ask-tool.ts +34 -4
  62. package/src/planning/system-prompt.ts +1 -1
  63. package/src/tool-catalog/active-tool-controller.ts +0 -22
  64. package/src/tool-catalog/active-tool-planner.ts +0 -26
  65. package/src/tool-catalog/tool-groups.ts +1 -9
  66. package/src/types.ts +87 -8
  67. package/src/ui-design/session.ts +114 -10
  68. package/src/utils/executable.ts +10 -1
  69. package/src/workspace/state-paths.ts +1 -1
  70. package/src/commands/mcp.ts +0 -814
  71. package/src/mcp/activation.ts +0 -77
  72. package/src/mcp/config.ts +0 -223
  73. package/src/mcp/docs.ts +0 -154
  74. package/src/mcp/gateway.ts +0 -103
  75. package/src/mcp/lifecycle.ts +0 -79
  76. package/src/mcp/manager-tool.ts +0 -104
  77. package/src/mcp/mcpc.ts +0 -113
  78. package/src/mcp/registry.ts +0 -98
  79. package/src/mcp/triggers.ts +0 -62
  80. package/src/mcp/types.ts +0 -95
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Fallback writer for `$GITHUB_STEP_SUMMARY`.
3
+ *
4
+ * GitHub Actions reads any markdown appended to the file at this env var and renders it
5
+ * on the workflow run summary page. We use it as a fail-open fallback when posting a PR
6
+ * comment is impossible (no auth, no `gh` CLI, no PR context).
7
+ *
8
+ * Never throws. Returns `{ ok: false }` quietly on IO error so the calling pipeline does
9
+ * not crash on a runner with an unwritable `$GITHUB_STEP_SUMMARY`.
10
+ */
11
+
12
+ import * as fs from "node:fs";
13
+
14
+ export interface WriteStepSummaryResult {
15
+ /** True when the body was appended (or no-op because env is unset). */
16
+ ok: boolean;
17
+ /** Resolved summary file path when appended. */
18
+ path?: string;
19
+ /** When ok=false, a short reason for diagnostics. */
20
+ reason?: string;
21
+ }
22
+
23
+ /**
24
+ * Append `body` to the workflow step summary file. When `$GITHUB_STEP_SUMMARY` is unset
25
+ * (local dev), this is a successful no-op.
26
+ */
27
+ export function writeStepSummary(
28
+ body: string,
29
+ env: NodeJS.ProcessEnv = process.env,
30
+ ): WriteStepSummaryResult {
31
+ const summaryPath = env.GITHUB_STEP_SUMMARY;
32
+ if (!summaryPath) {
33
+ return { ok: true };
34
+ }
35
+ try {
36
+ // The summary file is append-only between steps; we add a leading blank line so the
37
+ // harness section is separated from anything an earlier step contributed.
38
+ const payload = body.endsWith("\n") ? `\n${body}` : `\n${body}\n`;
39
+ fs.appendFileSync(summaryPath, payload);
40
+ return { ok: true, path: summaryPath };
41
+ } catch (error) {
42
+ return {
43
+ ok: false,
44
+ reason: error instanceof Error ? error.message : String(error),
45
+ };
46
+ }
47
+ }
@@ -52,6 +52,13 @@ export const HARNESS_IMPLEMENT_LOG_FILENAME = "implement-log.jsonl";
52
52
  export const HARNESS_PIPELINE_LOG_FILENAME = "pipeline-log.jsonl";
53
53
  export const HARNESS_RESEARCH_DIRNAME = "research";
54
54
 
55
+ /** Per-session staging directory for the docs stage. */
56
+ export const HARNESS_DOCS_STAGING_DIRNAME = "docs";
57
+ /** Layers subdirectory within the docs staging or repo-docs directory. */
58
+ export const HARNESS_DOCS_LAYERS_DIRNAME = "layers";
59
+ /** Index filename rendered after layer docs promote. */
60
+ export const HARNESS_DOCS_README_FILENAME = "README.md";
61
+
55
62
  /** Tier 1 / Tier 2 output paths in the repo. */
56
63
  export const HARNESS_AGENTS_MD_FILENAME = "AGENTS.md";
57
64
  export const HARNESS_DOCS_DIRNAME = "docs";
@@ -233,3 +240,91 @@ export function getHarnessGoldenPrinciplesPath(_paths: PlatformPaths, cwd: strin
233
240
  export function getHarnessFallowConfigPath(_paths: PlatformPaths, cwd: string): string {
234
241
  return path.join(cwd, HARNESS_FALLOW_CONFIG_FILENAME);
235
242
  }
243
+
244
+ // ---------------------------------------------------------------------------
245
+ // Docs stage — staging + repo paths.
246
+ // ---------------------------------------------------------------------------
247
+ /**
248
+ * Strict whitelist for per-layer doc identifiers.
249
+ *
250
+ * Layer ids are user/model-controlled (set in `design-spec.json`) and feed into
251
+ * `<repo>/docs/layers/<id>.md` plus the per-session staging mirror. Without a strict
252
+ * boundary an id like `../golden-principles` would let the docs stage overwrite Tier 1
253
+ * docs (or escape `docs/layers/` entirely). Reject anything that isn't a safe filename
254
+ * stem: alnum, hyphen, underscore, and (non-leading, non-consecutive) dots.
255
+ */
256
+ const LAYER_ID_PATTERN = /^[A-Za-z0-9_-][A-Za-z0-9._-]{0,63}$/;
257
+
258
+ export function isSafeLayerId(layerId: string): boolean {
259
+ return (
260
+ typeof layerId === "string" &&
261
+ LAYER_ID_PATTERN.test(layerId) &&
262
+ !layerId.includes("..")
263
+ );
264
+ }
265
+
266
+ export function assertSafeLayerId(layerId: string): void {
267
+ if (!isSafeLayerId(layerId)) {
268
+ throw new Error(
269
+ `harness: invalid layer id ${JSON.stringify(layerId)} — expected [A-Za-z0-9._-]{1,64} with no path separators, leading dot, or '..'.`,
270
+ );
271
+ }
272
+ }
273
+
274
+
275
+ /**
276
+ * Per-session staging dir for the docs stage:
277
+ * `<projectRoot>/sessions/<sid>/docs/`. Subagents write `layers/<id>.md` here; the stage
278
+ * renders the index and atomically promotes both staging files to the repo.
279
+ */
280
+ export function getHarnessDocsStagingDir(
281
+ paths: PlatformPaths,
282
+ cwd: string,
283
+ sessionId: string,
284
+ ): string {
285
+ return path.join(getHarnessSessionDir(paths, cwd, sessionId), HARNESS_DOCS_STAGING_DIRNAME);
286
+ }
287
+
288
+ /** Per-session staging path for a single layer doc. */
289
+ export function getHarnessDocsStagingLayerPath(
290
+ paths: PlatformPaths,
291
+ cwd: string,
292
+ sessionId: string,
293
+ layerId: string,
294
+ ): string {
295
+ assertSafeLayerId(layerId);
296
+ return path.join(
297
+ getHarnessDocsStagingDir(paths, cwd, sessionId),
298
+ HARNESS_DOCS_LAYERS_DIRNAME,
299
+ `${layerId}.md`,
300
+ );
301
+ }
302
+
303
+ /** Per-session staging path for the rendered docs index. */
304
+ export function getHarnessDocsStagingReadmePath(
305
+ paths: PlatformPaths,
306
+ cwd: string,
307
+ sessionId: string,
308
+ ): string {
309
+ return path.join(getHarnessDocsStagingDir(paths, cwd, sessionId), HARNESS_DOCS_README_FILENAME);
310
+ }
311
+
312
+ /** Repo-root path for the rendered docs index: `<repo>/docs/README.md`. */
313
+ export function getHarnessRepoDocsReadmePath(_paths: PlatformPaths, cwd: string): string {
314
+ return path.join(cwd, HARNESS_DOCS_DIRNAME, HARNESS_DOCS_README_FILENAME);
315
+ }
316
+
317
+ /** Repo-root path for a single per-layer doc: `<repo>/docs/layers/<id>.md`. */
318
+ export function getHarnessRepoDocsLayerPath(
319
+ _paths: PlatformPaths,
320
+ cwd: string,
321
+ layerId: string,
322
+ ): string {
323
+ assertSafeLayerId(layerId);
324
+ return path.join(cwd, HARNESS_DOCS_DIRNAME, HARNESS_DOCS_LAYERS_DIRNAME, `${layerId}.md`);
325
+ }
326
+
327
+ /** Repo-root directory hosting the per-layer docs: `<repo>/docs/layers/`. */
328
+ export function getHarnessRepoDocsLayersDir(_paths: PlatformPaths, cwd: string): string {
329
+ return path.join(cwd, HARNESS_DOCS_DIRNAME, HARNESS_DOCS_LAYERS_DIRNAME);
330
+ }
@@ -61,6 +61,7 @@ export function defaultCiConfigFromDiscover(discover: HarnessDiscoverArtifact):
61
61
  trigger: { mode: "branches", branches: ["dev", "main"] },
62
62
  localCommand,
63
63
  workflowPath: ".github/workflows/harness-quality.yml",
64
+ prComment: { enabled: true, mode: "every-push" },
64
65
  };
65
66
  }
66
67
 
@@ -190,7 +190,6 @@ function detectOmpInfra(cwd: string): HarnessDiscoverArtifact["ompInfra"] {
190
190
  const hasSupipowers = existsSync(supipowersDir);
191
191
  const skills: string[] = [];
192
192
  const reviewAgents: string[] = [];
193
- const mcpServers: string[] = [];
194
193
  let plansCount = 0;
195
194
 
196
195
  // Skills are typically in skills/ at the repo root.
@@ -226,20 +225,9 @@ function detectOmpInfra(cwd: string): HarnessDiscoverArtifact["ompInfra"] {
226
225
  plansCount = 0;
227
226
  }
228
227
  }
229
- const mcpJson = path.join(supipowersDir, ".mcp.json");
230
- if (existsSync(mcpJson)) {
231
- try {
232
- const parsed = JSON.parse(fs.readFileSync(mcpJson, "utf8")) as {
233
- servers?: Record<string, unknown>;
234
- };
235
- if (parsed.servers) mcpServers.push(...Object.keys(parsed.servers));
236
- } catch {
237
- // ignore
238
- }
239
- }
240
228
  }
241
229
 
242
- return { hasSupipowers, skills, reviewAgents, mcpServers, plansCount };
230
+ return { hasSupipowers, skills, reviewAgents, plansCount };
243
231
  }
244
232
 
245
233
  function detectFrameworks(cwd: string): string[] {