@groundnuty/macf 0.2.35 → 0.2.37

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 (105) hide show
  1. package/dist/.build-info.json +2 -2
  2. package/dist/cli/claude-sh.d.ts +12 -10
  3. package/dist/cli/claude-sh.d.ts.map +1 -1
  4. package/dist/cli/claude-sh.js +26 -13
  5. package/dist/cli/claude-sh.js.map +1 -1
  6. package/dist/cli/commands/certs.js +3 -3
  7. package/dist/cli/commands/certs.js.map +1 -1
  8. package/dist/cli/commands/init.d.ts.map +1 -1
  9. package/dist/cli/commands/init.js +10 -0
  10. package/dist/cli/commands/init.js.map +1 -1
  11. package/dist/cli/commands/monitor.d.ts +16 -0
  12. package/dist/cli/commands/monitor.d.ts.map +1 -0
  13. package/dist/cli/commands/monitor.js +96 -0
  14. package/dist/cli/commands/monitor.js.map +1 -0
  15. package/dist/cli/commands/propose.d.ts +21 -0
  16. package/dist/cli/commands/propose.d.ts.map +1 -0
  17. package/dist/cli/commands/propose.js +128 -0
  18. package/dist/cli/commands/propose.js.map +1 -0
  19. package/dist/cli/commands/rules-refresh.d.ts +1 -0
  20. package/dist/cli/commands/rules-refresh.d.ts.map +1 -1
  21. package/dist/cli/commands/rules-refresh.js +22 -1
  22. package/dist/cli/commands/rules-refresh.js.map +1 -1
  23. package/dist/cli/commands/update.d.ts.map +1 -1
  24. package/dist/cli/commands/update.js +23 -2
  25. package/dist/cli/commands/update.js.map +1 -1
  26. package/dist/cli/env-files-update.d.ts.map +1 -1
  27. package/dist/cli/env-files-update.js +5 -1
  28. package/dist/cli/env-files-update.js.map +1 -1
  29. package/dist/cli/env-files.d.ts +38 -13
  30. package/dist/cli/env-files.d.ts.map +1 -1
  31. package/dist/cli/env-files.js +73 -14
  32. package/dist/cli/env-files.js.map +1 -1
  33. package/dist/cli/index.js +109 -0
  34. package/dist/cli/index.js.map +1 -1
  35. package/dist/cli/monitor/digest.d.ts +89 -0
  36. package/dist/cli/monitor/digest.d.ts.map +1 -0
  37. package/dist/cli/monitor/digest.js +232 -0
  38. package/dist/cli/monitor/digest.js.map +1 -0
  39. package/dist/cli/monitor/github-reader.d.ts +38 -0
  40. package/dist/cli/monitor/github-reader.d.ts.map +1 -0
  41. package/dist/cli/monitor/github-reader.js +65 -0
  42. package/dist/cli/monitor/github-reader.js.map +1 -0
  43. package/dist/cli/monitor/reflections.d.ts +18 -0
  44. package/dist/cli/monitor/reflections.d.ts.map +1 -0
  45. package/dist/cli/monitor/reflections.js +72 -0
  46. package/dist/cli/monitor/reflections.js.map +1 -0
  47. package/dist/cli/monitor/run.d.ts +30 -0
  48. package/dist/cli/monitor/run.d.ts.map +1 -0
  49. package/dist/cli/monitor/run.js +67 -0
  50. package/dist/cli/monitor/run.js.map +1 -0
  51. package/dist/cli/project-rules.d.ts +105 -0
  52. package/dist/cli/project-rules.d.ts.map +1 -0
  53. package/dist/cli/project-rules.js +305 -0
  54. package/dist/cli/project-rules.js.map +1 -0
  55. package/dist/cli/propose/candidates.d.ts +95 -0
  56. package/dist/cli/propose/candidates.d.ts.map +1 -0
  57. package/dist/cli/propose/candidates.js +117 -0
  58. package/dist/cli/propose/candidates.js.map +1 -0
  59. package/dist/cli/propose/invariants.d.ts +49 -0
  60. package/dist/cli/propose/invariants.d.ts.map +1 -0
  61. package/dist/cli/propose/invariants.js +154 -0
  62. package/dist/cli/propose/invariants.js.map +1 -0
  63. package/dist/cli/propose/proposal-writer.d.ts +33 -0
  64. package/dist/cli/propose/proposal-writer.d.ts.map +1 -0
  65. package/dist/cli/propose/proposal-writer.js +53 -0
  66. package/dist/cli/propose/proposal-writer.js.map +1 -0
  67. package/dist/cli/propose/report.d.ts +49 -0
  68. package/dist/cli/propose/report.d.ts.map +1 -0
  69. package/dist/cli/propose/report.js +227 -0
  70. package/dist/cli/propose/report.js.map +1 -0
  71. package/dist/cli/propose/run.d.ts +41 -0
  72. package/dist/cli/propose/run.d.ts.map +1 -0
  73. package/dist/cli/propose/run.js +62 -0
  74. package/dist/cli/propose/run.js.map +1 -0
  75. package/dist/cli/settings-writer.d.ts +87 -6
  76. package/dist/cli/settings-writer.d.ts.map +1 -1
  77. package/dist/cli/settings-writer.js +141 -6
  78. package/dist/cli/settings-writer.js.map +1 -1
  79. package/dist/reconciler/parse-delivered.d.ts +32 -0
  80. package/dist/reconciler/parse-delivered.d.ts.map +1 -0
  81. package/dist/reconciler/parse-delivered.js +18 -0
  82. package/dist/reconciler/parse-delivered.js.map +1 -0
  83. package/dist/reconciler/parse-processed.d.ts +57 -0
  84. package/dist/reconciler/parse-processed.d.ts.map +1 -0
  85. package/dist/reconciler/parse-processed.js +41 -0
  86. package/dist/reconciler/parse-processed.js.map +1 -0
  87. package/dist/reconciler/reconcile.d.ts +130 -0
  88. package/dist/reconciler/reconcile.d.ts.map +1 -0
  89. package/dist/reconciler/reconcile.js +119 -0
  90. package/dist/reconciler/reconcile.js.map +1 -0
  91. package/dist/reconciler/run.d.ts +23 -0
  92. package/dist/reconciler/run.d.ts.map +1 -0
  93. package/dist/reconciler/run.js +273 -0
  94. package/dist/reconciler/run.js.map +1 -0
  95. package/package.json +2 -2
  96. package/plugin/rules/coordination.md +22 -13
  97. package/plugin/rules/gh-token-attribution-traps.md +4 -0
  98. package/plugin/rules/mention-routing-hygiene.md +2 -0
  99. package/plugin/rules/observability-wiring.md +3 -3
  100. package/plugin/rules/reflection-staging.md +65 -0
  101. package/plugin/rules/silent-fallback-hazards.md +64 -8
  102. package/scripts/check-auditor-never-acts.sh +167 -0
  103. package/scripts/check-gh-attribution.sh +230 -0
  104. package/scripts/emit-turn-receipt.sh +81 -0
  105. package/scripts/harvest-reflection.sh +125 -0
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Read-only Monitor orchestrator for the auditor (groundnuty/macf#502, DR-026 F4).
3
+ *
4
+ * Aggregates a project's coordination substrate — GitHub (open issues + open
5
+ * PRs, via an injectable read-only seam), F2 reflection ledgers, and a defensive
6
+ * project-tier rule count — and renders a protocol-health digest via the pure
7
+ * `buildDigest`.
8
+ *
9
+ * Everything here is a READ. The GitHub seam is `GitHubReader` (list-only by
10
+ * type); reflections + project-rule counts are filesystem reads. The clock is
11
+ * injected so output is deterministic under test.
12
+ */
13
+ import { readdirSync, existsSync, statSync } from 'node:fs';
14
+ import { join } from 'node:path';
15
+ import { buildDigest } from './digest.js';
16
+ import { readReflections } from './reflections.js';
17
+ /**
18
+ * Count `.claude/rules/project/*.md` files. Defensive: returns 0 (never throws)
19
+ * when the directory is absent — F3 isn't merged, so its absence is normal.
20
+ */
21
+ export function countProjectRules(workspaceDir) {
22
+ const dir = join(workspaceDir, '.claude', 'rules', 'project');
23
+ if (!existsSync(dir))
24
+ return 0;
25
+ try {
26
+ return readdirSync(dir).filter((f) => {
27
+ if (!f.endsWith('.md'))
28
+ return false;
29
+ try {
30
+ return statSync(join(dir, f)).isFile();
31
+ }
32
+ catch {
33
+ return false;
34
+ }
35
+ }).length;
36
+ }
37
+ catch {
38
+ return 0;
39
+ }
40
+ }
41
+ /**
42
+ * Run the Monitor: read everything, build the digest, return the Markdown.
43
+ *
44
+ * Only READS are performed. Any GitHub read failure is surfaced as a thrown
45
+ * error to the caller (the CLI command), which decides how to report it.
46
+ */
47
+ export async function runMonitor(opts) {
48
+ const [issues, prs] = await Promise.all([
49
+ opts.reader.listOpenIssues(opts.repo),
50
+ opts.reader.listOpenPRs(opts.repo),
51
+ ]);
52
+ const reflections = readReflections(opts.reflectionsDir);
53
+ const projectRulesCount = countProjectRules(opts.workspaceDir);
54
+ return buildDigest({
55
+ project: opts.project,
56
+ repo: opts.repo,
57
+ sinceDays: opts.sinceDays,
58
+ issues,
59
+ prs,
60
+ reflections: reflections.records,
61
+ reflectionsSkipped: reflections.skipped,
62
+ reflectionFiles: reflections.files,
63
+ projectRulesCount,
64
+ now: opts.now,
65
+ });
66
+ }
67
+ //# sourceMappingURL=run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../../../src/cli/monitor/run.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC5D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,WAAW,EAAc,MAAM,aAAa,CAAC;AAEtD,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAkBnD;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,YAAoB;IACpD,MAAM,GAAG,GAAG,IAAI,CAAC,YAAY,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC;IAC9D,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,CAAC,CAAC;IAC/B,IAAI,CAAC;QACH,OAAO,WAAW,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE;YACnC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC;gBAAE,OAAO,KAAK,CAAC;YACrC,IAAI,CAAC;gBACH,OAAO,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;YACzC,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,KAAK,CAAC;YACf,CAAC;QACH,CAAC,CAAC,CAAC,MAAM,CAAC;IACZ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,CAAC;IACX,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,IAAuB;IACtD,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QACtC,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC;QACrC,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC;KACnC,CAAC,CAAC;IAEH,MAAM,WAAW,GAAG,eAAe,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IACzD,MAAM,iBAAiB,GAAG,iBAAiB,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAE/D,OAAO,WAAW,CAAC;QACjB,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,MAAM;QACN,GAAG;QACH,WAAW,EAAE,WAAW,CAAC,OAAO;QAChC,kBAAkB,EAAE,WAAW,CAAC,OAAO;QACvC,eAAe,EAAE,WAAW,CAAC,KAAK;QAClC,iBAAiB;QACjB,GAAG,EAAE,IAAI,CAAC,GAAG;KACd,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,105 @@
1
+ /** Env var carrying the project-rules source (operator-managed config). */
2
+ export declare const PROJECT_RULES_SOURCE_ENV = "MACF_PROJECT_RULES_SOURCE";
3
+ /**
4
+ * Destination directory for project-tier rules within a workspace:
5
+ * `<workspace>/.claude/rules/project/`. A SUBDIR of the universal-rule dir so
6
+ * the two tiers are distinguishable on disk.
7
+ */
8
+ export declare function workspaceProjectRulesDir(workspaceDir: string): string;
9
+ /**
10
+ * Parsed shape of a `MACF_PROJECT_RULES_SOURCE` value.
11
+ *
12
+ * - `kind: 'github'` — `<owner>/<repo>//<path>` form: fetched via git clone.
13
+ * - `kind: 'local'` — a local directory path: copied directly.
14
+ */
15
+ export type ProjectRulesSource = {
16
+ readonly kind: 'github';
17
+ readonly owner: string;
18
+ readonly repo: string;
19
+ readonly subpath: string;
20
+ } | {
21
+ readonly kind: 'local';
22
+ readonly dir: string;
23
+ };
24
+ /**
25
+ * Parse a `MACF_PROJECT_RULES_SOURCE` value into a discriminated source.
26
+ *
27
+ * The `//` separator disambiguates the two forms unambiguously: a GitHub
28
+ * `<owner>/<repo>//<path>` always contains `//`, a local filesystem path
29
+ * never does (`//` collapses to `/` on POSIX and isn't a meaningful path
30
+ * token). So presence of `//` selects the github form; absence selects local.
31
+ *
32
+ * Returns `undefined` for an empty/whitespace-only value (caller treats as
33
+ * no-op) or a malformed github form (missing owner/repo/path segment) —
34
+ * malformed is surfaced by the caller as a warning, not a parse crash.
35
+ */
36
+ export declare function parseProjectRulesSource(raw: string | undefined): ProjectRulesSource | undefined;
37
+ export interface FetchProjectRulesOptions {
38
+ /** Override the env value (defaults to process.env[MACF_PROJECT_RULES_SOURCE]). */
39
+ readonly source?: string;
40
+ /**
41
+ * Override the GitHub base URL for the github-source form (tests point this
42
+ * at a local bare repo). Defaults to `https://github.com`.
43
+ */
44
+ readonly githubBaseUrl?: string;
45
+ }
46
+ /**
47
+ * Distribute project-tier rules into `<workspace>/.claude/rules/project/`.
48
+ *
49
+ * Resolves the source from `MACF_PROJECT_RULES_SOURCE` (or `options.source`):
50
+ * - Unset/empty/whitespace → NO-OP, returns `[]`. The tier is optional.
51
+ * - `<owner>/<repo>//<path>` → shallow git clone the default branch (mirrors
52
+ * plugin-fetcher.ts), copy `<clone>/<path>/*.md` to the dest subdir.
53
+ * - a local directory path → copy `<dir>/*.md` directly.
54
+ *
55
+ * Only `*.md` files are copied (a `.example` template, a README, etc. in the
56
+ * source are ignored — same filter as the universal-rule copy). Each copied
57
+ * file gets the PROJECT_RULE_HEADER prepended (unless it already opens with an
58
+ * HTML comment, to avoid double-stacking).
59
+ *
60
+ * **Idempotent + non-destructive to other tiers.** The dest is a fresh subdir;
61
+ * the universal `.claude/rules/*.md` files are never touched (different path).
62
+ * Re-running overwrites the project subdir's `*.md` files with the current
63
+ * source content. A `.example` seed (from `macf init`) is preserved — only
64
+ * `*.md` files are managed here.
65
+ *
66
+ * Throws only on a genuine fetch failure (git clone error, source path is a
67
+ * file not a directory, the github subpath is missing in the clone). A
68
+ * malformed source string is a no-op-with-warning, NOT a throw, so a typo'd
69
+ * config can't break `macf update`.
70
+ *
71
+ * @returns the list of copied basenames (empty when the source is unset or
72
+ * the source dir has no `*.md` files).
73
+ */
74
+ export declare function fetchProjectRules(workspaceDir: string, options?: FetchProjectRulesOptions): readonly string[];
75
+ /**
76
+ * Basename of the seed template `macf init` drops into the project-rules
77
+ * subdir. The `.example` suffix keeps it OUT of the live-rule set (only `*.md`
78
+ * are loaded as rules + managed by `fetchProjectRules`), so it self-documents
79
+ * the tier + gives the DR-026 §4 auditor a format to match without becoming a
80
+ * rule itself.
81
+ */
82
+ export declare const PROJECT_RULE_SEED_NAME = "EXAMPLE.project-rule.md.example";
83
+ /**
84
+ * Generic, deployment-agnostic seed content for the project-rules tier.
85
+ *
86
+ * CRITICAL: this ships via `macf init` to EVERY deployment, so it must NOT be
87
+ * macf-specific. A genomics deployment must not receive a macf `dev.mk` rule.
88
+ * The body demonstrates the FORMAT + the precedence contract with neutral
89
+ * placeholders only — never a concrete macf rule.
90
+ */
91
+ export declare function projectRuleSeedContent(): string;
92
+ /**
93
+ * Seed the project-rules subdir with the generic `.example` template
94
+ * (`macf init`). Creates `.claude/rules/project/` (mkdir -p) and writes the
95
+ * `.example` file. Idempotent: overwrites the `.example` with the current
96
+ * canonical template — it's a managed example, not operator state. Returns the
97
+ * seeded basename.
98
+ *
99
+ * Deliberately does NOT fetch from `MACF_PROJECT_RULES_SOURCE` — `macf init`
100
+ * only lays down the tier + its self-documenting seed; `macf update` (and
101
+ * `macf rules refresh`) pull the actual rules from the source. This keeps init
102
+ * generic (it ships to every deployment) and offline-safe.
103
+ */
104
+ export declare function seedProjectRulesDir(workspaceDir: string): string;
105
+ //# sourceMappingURL=project-rules.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-rules.d.ts","sourceRoot":"","sources":["../../src/cli/project-rules.ts"],"names":[],"mappings":"AAiDA,2EAA2E;AAC3E,eAAO,MAAM,wBAAwB,8BAA8B,CAAC;AAsBpE;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAErE;AAED;;;;;GAKG;AACH,MAAM,MAAM,kBAAkB,GAC1B;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACpG;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAErD;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,kBAAkB,GAAG,SAAS,CAmB/F;AAED,MAAM,WAAW,wBAAwB;IACvC,mFAAmF;IACnF,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;CACjC;AAID;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,iBAAiB,CAC/B,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE,wBAA6B,GACrC,SAAS,MAAM,EAAE,CA0DnB;AAwBD;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,oCAAoC,CAAC;AAExE;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CA8D/C;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAKhE"}
@@ -0,0 +1,305 @@
1
+ /**
2
+ * Project-tier rule distribution (groundnuty/macf#501, DR-026 F3).
3
+ *
4
+ * The three-tier rule model (DR-026 §3) splits coordination knowledge into:
5
+ * 1. Universal / product rules — shipped IN the CLI at `plugin/rules/*.md`,
6
+ * copied to `.claude/rules/*.md` (see rules.ts `copyCanonicalRules`).
7
+ * 2. **Project rules** — per-deployment, NOT shipped in the npm product. This
8
+ * module. Sourced from an explicit config and copied to a SUBDIR
9
+ * `.claude/rules/project/*.md` so the universal and project tiers are
10
+ * distinguishable on disk (DR-026 §4's subordination check needs a clean
11
+ * target to match against).
12
+ * 3. Agent-private rules — an agent's own `.claude/rules/` / memory; out of
13
+ * scope here.
14
+ *
15
+ * **Why explicit config, not "the coordination repo".** A multi-repo deployment
16
+ * has no single repo to implicitly pull project rules from, so the source MUST
17
+ * be configured. `MACF_PROJECT_RULES_SOURCE` carries it, in one of two forms:
18
+ *
19
+ * - `<owner>/<repo>//<path>` — a GitHub repo + subdir (e.g.
20
+ * `groundnuty/macf//project-rules`). Fetched via
21
+ * a shallow `git clone` of the default branch,
22
+ * mirroring plugin-fetcher.ts (reuse, don't
23
+ * reinvent). The `//` separates repo from path.
24
+ * - a local directory path — anything WITHOUT the `//` separator that
25
+ * resolves to a directory on disk. Copied
26
+ * directly (no clone). Lets a single-host
27
+ * deployment point at a checked-out repo or a
28
+ * plain rules directory.
29
+ *
30
+ * **Optional tier — never errors on absence.** When the env var is unset/empty,
31
+ * `fetchProjectRules` is a no-op returning `[]`. The tier is optional, exactly
32
+ * like a workspace that has no universal rules: absence is normal, not a fault.
33
+ *
34
+ * **No shadow of the universal tier.** Project rules land under
35
+ * `.claude/rules/project/`, a different subdir from the universal
36
+ * `.claude/rules/*.md`. The universal-rule copy path (rules.ts) is untouched,
37
+ * so this can never overwrite a universal rule.
38
+ *
39
+ * **Precedence (documented, NOT enforced here).** Project rules *add to /
40
+ * specialize* the universal protocol; they never weaken it. The §4
41
+ * invariant-check that enforces "never contradict" is a separate (gated) slice
42
+ * of DR-026 — F3 only lays down the on-disk tiers. See
43
+ * `design/project-tier-rules.md`.
44
+ */
45
+ import { execFileSync } from 'node:child_process';
46
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from 'node:fs';
47
+ import { tmpdir } from 'node:os';
48
+ import { join, resolve } from 'node:path';
49
+ /** Env var carrying the project-rules source (operator-managed config). */
50
+ export const PROJECT_RULES_SOURCE_ENV = 'MACF_PROJECT_RULES_SOURCE';
51
+ /**
52
+ * Header prepended to each copied project rule, marking the on-disk tier +
53
+ * the managed-from-source contract. Distinct wording from the universal-rule
54
+ * MANAGED_HEADER (rules.ts) so an operator inspecting a file knows which tier
55
+ * it belongs to + where it came from.
56
+ */
57
+ const PROJECT_RULE_HEADER = [
58
+ '<!--',
59
+ ' PROJECT-TIER RULE (DR-026 §3 tier 2). Distributed by `macf` from this',
60
+ ` deployment's project-rules source (\`${PROJECT_RULES_SOURCE_ENV}\`). Do not`,
61
+ ' edit this workspace copy — edits are overwritten on the next `macf update`.',
62
+ ' Edit the rule at the source, then re-run `macf update` here.',
63
+ '',
64
+ ' Project rules ADD TO / SPECIALIZE the universal rules in',
65
+ ' .claude/rules/*.md; they must never contradict or weaken them',
66
+ ' (DR-026 §4). See design/project-tier-rules.md.',
67
+ '-->',
68
+ '',
69
+ ].join('\n');
70
+ /**
71
+ * Destination directory for project-tier rules within a workspace:
72
+ * `<workspace>/.claude/rules/project/`. A SUBDIR of the universal-rule dir so
73
+ * the two tiers are distinguishable on disk.
74
+ */
75
+ export function workspaceProjectRulesDir(workspaceDir) {
76
+ return join(resolve(workspaceDir), '.claude', 'rules', 'project');
77
+ }
78
+ /**
79
+ * Parse a `MACF_PROJECT_RULES_SOURCE` value into a discriminated source.
80
+ *
81
+ * The `//` separator disambiguates the two forms unambiguously: a GitHub
82
+ * `<owner>/<repo>//<path>` always contains `//`, a local filesystem path
83
+ * never does (`//` collapses to `/` on POSIX and isn't a meaningful path
84
+ * token). So presence of `//` selects the github form; absence selects local.
85
+ *
86
+ * Returns `undefined` for an empty/whitespace-only value (caller treats as
87
+ * no-op) or a malformed github form (missing owner/repo/path segment) —
88
+ * malformed is surfaced by the caller as a warning, not a parse crash.
89
+ */
90
+ export function parseProjectRulesSource(raw) {
91
+ const value = (raw ?? '').trim();
92
+ if (value === '')
93
+ return undefined;
94
+ const sepIdx = value.indexOf('//');
95
+ if (sepIdx < 0) {
96
+ // No `//` → local directory path.
97
+ return { kind: 'local', dir: value };
98
+ }
99
+ const repoPart = value.slice(0, sepIdx);
100
+ const subpath = value.slice(sepIdx + 2).replace(/^\/+|\/+$/g, '');
101
+ const ownerRepo = repoPart.split('/');
102
+ if (ownerRepo.length !== 2 || !ownerRepo[0] || !ownerRepo[1] || subpath === '') {
103
+ // Malformed github form (e.g. "owner//path" with no repo, or trailing
104
+ // empty subpath). Don't throw — caller warns + skips.
105
+ return undefined;
106
+ }
107
+ return { kind: 'github', owner: ownerRepo[0], repo: ownerRepo[1], subpath };
108
+ }
109
+ const DEFAULT_GITHUB_BASE_URL = 'https://github.com';
110
+ /**
111
+ * Distribute project-tier rules into `<workspace>/.claude/rules/project/`.
112
+ *
113
+ * Resolves the source from `MACF_PROJECT_RULES_SOURCE` (or `options.source`):
114
+ * - Unset/empty/whitespace → NO-OP, returns `[]`. The tier is optional.
115
+ * - `<owner>/<repo>//<path>` → shallow git clone the default branch (mirrors
116
+ * plugin-fetcher.ts), copy `<clone>/<path>/*.md` to the dest subdir.
117
+ * - a local directory path → copy `<dir>/*.md` directly.
118
+ *
119
+ * Only `*.md` files are copied (a `.example` template, a README, etc. in the
120
+ * source are ignored — same filter as the universal-rule copy). Each copied
121
+ * file gets the PROJECT_RULE_HEADER prepended (unless it already opens with an
122
+ * HTML comment, to avoid double-stacking).
123
+ *
124
+ * **Idempotent + non-destructive to other tiers.** The dest is a fresh subdir;
125
+ * the universal `.claude/rules/*.md` files are never touched (different path).
126
+ * Re-running overwrites the project subdir's `*.md` files with the current
127
+ * source content. A `.example` seed (from `macf init`) is preserved — only
128
+ * `*.md` files are managed here.
129
+ *
130
+ * Throws only on a genuine fetch failure (git clone error, source path is a
131
+ * file not a directory, the github subpath is missing in the clone). A
132
+ * malformed source string is a no-op-with-warning, NOT a throw, so a typo'd
133
+ * config can't break `macf update`.
134
+ *
135
+ * @returns the list of copied basenames (empty when the source is unset or
136
+ * the source dir has no `*.md` files).
137
+ */
138
+ export function fetchProjectRules(workspaceDir, options = {}) {
139
+ const raw = options.source ?? process.env[PROJECT_RULES_SOURCE_ENV];
140
+ const parsed = parseProjectRulesSource(raw);
141
+ if (parsed === undefined) {
142
+ if ((raw ?? '').trim() !== '') {
143
+ // Non-empty but unparseable — surface it; don't silently drop.
144
+ process.stderr.write(`Warning: ${PROJECT_RULES_SOURCE_ENV}="${raw}" is malformed.\n` +
145
+ ` Expected "<owner>/<repo>//<path>" (e.g. groundnuty/macf//project-rules)\n` +
146
+ ` or a local directory path. Skipping project-rule distribution.\n`);
147
+ }
148
+ return [];
149
+ }
150
+ if (parsed.kind === 'local') {
151
+ const srcDir = resolve(parsed.dir);
152
+ if (!existsSync(srcDir)) {
153
+ throw new Error(`${PROJECT_RULES_SOURCE_ENV} local source does not exist: ${srcDir}`);
154
+ }
155
+ if (!statSync(srcDir).isDirectory()) {
156
+ throw new Error(`${PROJECT_RULES_SOURCE_ENV} local source is not a directory: ${srcDir}`);
157
+ }
158
+ return copyMarkdownRules(srcDir, workspaceProjectRulesDir(workspaceDir));
159
+ }
160
+ // github form — shallow-clone the default branch (mirrors plugin-fetcher.ts),
161
+ // copy the subpath's *.md files, discard the clone.
162
+ const baseUrl = options.githubBaseUrl ?? DEFAULT_GITHUB_BASE_URL;
163
+ const repoUrl = `${baseUrl}/${parsed.owner}/${parsed.repo}`;
164
+ const tmpClone = mkdtempSync(join(tmpdir(), 'macf-project-rules-clone-'));
165
+ try {
166
+ execFileSync('git', ['clone', '--depth', '1', repoUrl, tmpClone], {
167
+ stdio: ['ignore', 'ignore', 'pipe'],
168
+ });
169
+ const srcDir = join(tmpClone, parsed.subpath);
170
+ if (!existsSync(srcDir) || !statSync(srcDir).isDirectory()) {
171
+ throw new Error(`Project-rules subpath "${parsed.subpath}" not found in ${repoUrl}. ` +
172
+ `Check ${PROJECT_RULES_SOURCE_ENV} and the repo layout.`);
173
+ }
174
+ return copyMarkdownRules(srcDir, workspaceProjectRulesDir(workspaceDir));
175
+ }
176
+ catch (err) {
177
+ if (err instanceof Error && err.message.includes('subpath'))
178
+ throw err;
179
+ const msg = err instanceof Error ? err.message : String(err);
180
+ throw new Error(`Failed to fetch project rules from ${repoUrl}: ${msg}. ` +
181
+ `Check network access + that ${PROJECT_RULES_SOURCE_ENV} is correct.`, { cause: err });
182
+ }
183
+ finally {
184
+ rmSync(tmpClone, { recursive: true, force: true });
185
+ }
186
+ }
187
+ /**
188
+ * Copy every `*.md` file from `srcDir` to `destDir`, prepending the
189
+ * project-rule header. Creates `destDir` (mkdir -p). Returns copied basenames.
190
+ * Shared by both the local and github source paths.
191
+ */
192
+ function copyMarkdownRules(srcDir, destDir) {
193
+ mkdirSync(destDir, { recursive: true });
194
+ const copied = [];
195
+ for (const entry of readdirSync(srcDir, { withFileTypes: true })) {
196
+ if (!entry.isFile() || !entry.name.endsWith('.md'))
197
+ continue;
198
+ const content = readFileSync(join(srcDir, entry.name), 'utf-8');
199
+ const out = content.startsWith('<!--') ? content : PROJECT_RULE_HEADER + content;
200
+ writeFileSync(join(destDir, entry.name), out);
201
+ copied.push(entry.name);
202
+ }
203
+ return copied;
204
+ }
205
+ // ---------------------------------------------------------------------------
206
+ // Seed (.example) template — written by `macf init`
207
+ // ---------------------------------------------------------------------------
208
+ /**
209
+ * Basename of the seed template `macf init` drops into the project-rules
210
+ * subdir. The `.example` suffix keeps it OUT of the live-rule set (only `*.md`
211
+ * are loaded as rules + managed by `fetchProjectRules`), so it self-documents
212
+ * the tier + gives the DR-026 §4 auditor a format to match without becoming a
213
+ * rule itself.
214
+ */
215
+ export const PROJECT_RULE_SEED_NAME = 'EXAMPLE.project-rule.md.example';
216
+ /**
217
+ * Generic, deployment-agnostic seed content for the project-rules tier.
218
+ *
219
+ * CRITICAL: this ships via `macf init` to EVERY deployment, so it must NOT be
220
+ * macf-specific. A genomics deployment must not receive a macf `dev.mk` rule.
221
+ * The body demonstrates the FORMAT + the precedence contract with neutral
222
+ * placeholders only — never a concrete macf rule.
223
+ */
224
+ export function projectRuleSeedContent() {
225
+ return [
226
+ '<!--',
227
+ ' EXAMPLE project-tier rule (template, not active).',
228
+ '',
229
+ ' This file ends in `.example`, so `macf` does NOT load it as a live rule',
230
+ ' and does NOT overwrite it on `macf update` (only `*.md` files in this',
231
+ ' directory are managed). It self-documents the project-rules tier and',
232
+ ' gives the DR-026 §4 auditor a format to match.',
233
+ '-->',
234
+ '',
235
+ '# Project-tier rules (`.claude/rules/project/`)',
236
+ '',
237
+ 'This directory holds **project-tier** coordination rules (DR-026 §3, tier 2):',
238
+ 'per-deployment rules that ADD TO or SPECIALIZE the universal rules in the',
239
+ 'parent `.claude/rules/*.md` directory.',
240
+ '',
241
+ '## Where these come from',
242
+ '',
243
+ 'Project rules are distributed by `macf` from this deployment\'s configured',
244
+ 'source, set via the `MACF_PROJECT_RULES_SOURCE` operator config in',
245
+ '`.claude/.macf/env.project-rules`. The source is one of:',
246
+ '',
247
+ '- `<owner>/<repo>//<path>` — a GitHub repo + subdir',
248
+ ' (e.g. `your-org/your-coordination-repo//project-rules`)',
249
+ '- a local directory path — for single-host deployments',
250
+ '',
251
+ 'When `MACF_PROJECT_RULES_SOURCE` is unset, this tier is empty (it is',
252
+ 'optional). Authored rules live at the SOURCE, not in this workspace copy —',
253
+ 'edit them there, then run `macf update` here.',
254
+ '',
255
+ '## Precedence (the one hard contract)',
256
+ '',
257
+ 'Project rules may **add to / specialize** the universal protocol but must',
258
+ '**never contradict or weaken** its protected invariants (e.g. reporter-owns',
259
+ 'closure, the identity↔attribution guarantee, the no-self-merge / LGTM gate).',
260
+ 'See `design/project-tier-rules.md` for the full precedence model.',
261
+ '',
262
+ '## Format to follow (replace this example)',
263
+ '',
264
+ 'A project rule is a Markdown file. Name it `*.md` (this `.example` is a',
265
+ 'template, not a rule). Suggested shape — adapt to your deployment:',
266
+ '',
267
+ '```markdown',
268
+ '# <Short rule title>',
269
+ '',
270
+ '## Applies to',
271
+ '',
272
+ '<Which agents / repos / situations this project rule governs.>',
273
+ '',
274
+ '## Rule',
275
+ '',
276
+ '<The specialization. State what it ADDS to the universal protocol — never',
277
+ 'what it weakens. If it looks like it relaxes a universal invariant, it is',
278
+ 'wrong by construction (DR-026 §4).>',
279
+ '',
280
+ '## Rationale',
281
+ '',
282
+ '<Why this deployment needs it. Link the incident / pattern it codifies.>',
283
+ '```',
284
+ '',
285
+ ].join('\n');
286
+ }
287
+ /**
288
+ * Seed the project-rules subdir with the generic `.example` template
289
+ * (`macf init`). Creates `.claude/rules/project/` (mkdir -p) and writes the
290
+ * `.example` file. Idempotent: overwrites the `.example` with the current
291
+ * canonical template — it's a managed example, not operator state. Returns the
292
+ * seeded basename.
293
+ *
294
+ * Deliberately does NOT fetch from `MACF_PROJECT_RULES_SOURCE` — `macf init`
295
+ * only lays down the tier + its self-documenting seed; `macf update` (and
296
+ * `macf rules refresh`) pull the actual rules from the source. This keeps init
297
+ * generic (it ships to every deployment) and offline-safe.
298
+ */
299
+ export function seedProjectRulesDir(workspaceDir) {
300
+ const destDir = workspaceProjectRulesDir(workspaceDir);
301
+ mkdirSync(destDir, { recursive: true });
302
+ writeFileSync(join(destDir, PROJECT_RULE_SEED_NAME), projectRuleSeedContent());
303
+ return PROJECT_RULE_SEED_NAME;
304
+ }
305
+ //# sourceMappingURL=project-rules.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-rules.js","sourceRoot":"","sources":["../../src/cli/project-rules.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Pure candidate-proposal pipeline for the auditor Plan membrane
3
+ * (groundnuty/macf#503, DR-026 G1). This is the Analyze→Plan half of the
4
+ * MAPE-K loop: turn F4's reflection signals into RATIFIABLE proposals.
5
+ *
6
+ * Everything here is deterministic CODE scaffolding. The LLM judgment (is this a
7
+ * real rule? is the text precise? is the tier right?) is NOT encoded — the code
8
+ * assembles the AGENT-AUTHORED signal content (`signal`/`proposed_tier`/
9
+ * `rationale` from `rule_evolution_signals`) into structured proposals and
10
+ * applies three LOAD-BEARING, mechanical safety gates:
11
+ *
12
+ * GATE 1 — N>1 = distinct AGENTS, not occurrences. A candidate is promotable
13
+ * only if it recurs across ≥ threshold DISTINCT `agent.name`s. Five hits from
14
+ * ONE agent is N=1 → HELD (reflection ≠ verification). We count the set of
15
+ * distinct agent names, never raw occurrences.
16
+ *
17
+ * GATE 3 — no-auto-drop on invariant-touch. Step 4 SURFACES which protected
18
+ * invariants a candidate touches + flags apparent-relaxations HIGH-RISK, but
19
+ * NEVER drops/rejects in code. The amendment clause lets the auditor PROPOSE
20
+ * an operator-ratified amendment, so auto-dropping would foreclose that path.
21
+ * (GATE 2 — dry-run-by-default — lives in the orchestrator/command + writer
22
+ * seam, not here.)
23
+ *
24
+ * The tier-router (step 3) groups by the `proposed_tier` HINT and marks
25
+ * `universal` candidates NEEDS-CONFIRMATION (never auto-route a universal
26
+ * promotion — it affects every deployment). The hint is a hint, not a decision.
27
+ */
28
+ import type { ReflectionRecord } from '@groundnuty/macf-core';
29
+ import { type InvariantTouch, type ProtectedInvariant } from './invariants.js';
30
+ /** Default distinct-agent threshold for promotion (configurable). */
31
+ export declare const DEFAULT_MIN_AGENTS = 2;
32
+ /**
33
+ * The router routing decision for a candidate, derived from its `proposed_tier`
34
+ * HINT. `needs-confirmation` is the universal/canonical case (never auto-route);
35
+ * `project-draft` is the local-project-rule case; `review` is anything else (an
36
+ * unrecognised tier hint still surfaces, routed for plain operator review).
37
+ */
38
+ export type RouteDecision = 'needs-confirmation' | 'project-draft' | 'review';
39
+ /** A fully-assembled, ratifiable candidate proposal (a survivor of GATE 1). */
40
+ export interface ProposalCandidate {
41
+ /** Dedup handle: the signal `key` when present, else the signal text. */
42
+ readonly handle: string;
43
+ /** Whether the grouping used an explicit `key` (vs falling back to text). */
44
+ readonly hasKey: boolean;
45
+ /** The agent-authored proposed tier HINT (verbatim). */
46
+ readonly proposedTier: string;
47
+ /** Representative agent-authored signal text. */
48
+ readonly signal: string;
49
+ /** All distinct agent-authored rationales contributing this candidate. */
50
+ readonly rationales: readonly string[];
51
+ /** Distinct agent names that corroborated this candidate (GATE 1 unit). */
52
+ readonly corroboratingAgents: readonly string[];
53
+ /** Distinct-agent count == corroboratingAgents.length (the GATE-1 number). */
54
+ readonly distinctAgents: number;
55
+ /** Raw occurrence count (records carrying this signal) — informational only. */
56
+ readonly occurrences: number;
57
+ /** Router decision from the tier hint. */
58
+ readonly route: RouteDecision;
59
+ /** Protected invariants this candidate plausibly touches (SURFACED, GATE 3). */
60
+ readonly invariantTouches: readonly InvariantTouch[];
61
+ /** True when the text reads like a relaxation → HIGH-RISK flag (GATE 3). */
62
+ readonly highRisk: boolean;
63
+ }
64
+ /** A candidate HELD below the distinct-agent threshold (reported, not dropped). */
65
+ export interface HeldCandidate {
66
+ readonly handle: string;
67
+ readonly hasKey: boolean;
68
+ readonly proposedTier: string;
69
+ readonly signal: string;
70
+ readonly distinctAgents: number;
71
+ readonly occurrences: number;
72
+ /** Why it was held — always the N<threshold reason for v1. */
73
+ readonly reason: string;
74
+ }
75
+ /** The full pipeline output: survivors + held + the threshold used. */
76
+ export interface CandidateSet {
77
+ readonly minAgents: number;
78
+ readonly promoted: readonly ProposalCandidate[];
79
+ readonly held: readonly HeldCandidate[];
80
+ }
81
+ /** Route a candidate from its tier hint (universal/canonical never auto-route). */
82
+ export declare function routeForTier(proposedTier: string): RouteDecision;
83
+ /**
84
+ * Run the full candidate pipeline over the reflection records.
85
+ *
86
+ * Steps (DR-026 G1 §The command):
87
+ * 1. Aggregate signals (distinct-agent grouping).
88
+ * 2. GATE 1 — keep candidates with distinctAgents ≥ minAgents; the rest go to
89
+ * `held` (visible, never silently dropped).
90
+ * 3. Tier-router — universal/canonical → needs-confirmation; project → draft.
91
+ * 4. Subordination-check — surface touched invariants + HIGH-RISK relaxation
92
+ * flag. NEVER drops (GATE 3).
93
+ */
94
+ export declare function buildCandidates(records: readonly ReflectionRecord[], invariants: readonly ProtectedInvariant[], minAgents?: number): CandidateSet;
95
+ //# sourceMappingURL=candidates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"candidates.d.ts","sourceRoot":"","sources":["../../../src/cli/propose/candidates.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAuB,MAAM,uBAAuB,CAAC;AACnF,OAAO,EAGL,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACxB,MAAM,iBAAiB,CAAC;AAEzB,qEAAqE;AACrE,eAAO,MAAM,kBAAkB,IAAI,CAAC;AAKpC;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GAAG,oBAAoB,GAAG,eAAe,GAAG,QAAQ,CAAC;AAE9E,+EAA+E;AAC/E,MAAM,WAAW,iBAAiB;IAChC,yEAAyE;IACzE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,6EAA6E;IAC7E,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,wDAAwD;IACxD,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,iDAAiD;IACjD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,0EAA0E;IAC1E,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,2EAA2E;IAC3E,QAAQ,CAAC,mBAAmB,EAAE,SAAS,MAAM,EAAE,CAAC;IAChD,8EAA8E;IAC9E,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,gFAAgF;IAChF,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,0CAA0C;IAC1C,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAC9B,gFAAgF;IAChF,QAAQ,CAAC,gBAAgB,EAAE,SAAS,cAAc,EAAE,CAAC;IACrD,4EAA4E;IAC5E,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AAED,mFAAmF;AACnF,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,8DAA8D;IAC9D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,uEAAuE;AACvE,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAChD,QAAQ,CAAC,IAAI,EAAE,SAAS,aAAa,EAAE,CAAC;CACzC;AA6DD,mFAAmF;AACnF,wBAAgB,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,aAAa,CAKhE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,SAAS,gBAAgB,EAAE,EACpC,UAAU,EAAE,SAAS,kBAAkB,EAAE,EACzC,SAAS,GAAE,MAA2B,GACrC,YAAY,CA2Dd"}