@massu/core 1.4.0 → 1.5.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 (60) hide show
  1. package/dist/cli.js +9445 -5483
  2. package/dist/hooks/auto-learning-pipeline.js +18 -0
  3. package/dist/hooks/classify-failure.js +18 -0
  4. package/dist/hooks/cost-tracker.js +18 -0
  5. package/dist/hooks/fix-detector.js +18 -0
  6. package/dist/hooks/incident-pipeline.js +18 -0
  7. package/dist/hooks/post-edit-context.js +18 -0
  8. package/dist/hooks/post-tool-use.js +18 -0
  9. package/dist/hooks/pre-compact.js +18 -0
  10. package/dist/hooks/pre-delete-check.js +18 -0
  11. package/dist/hooks/quality-event.js +18 -0
  12. package/dist/hooks/rule-enforcement-pipeline.js +18 -0
  13. package/dist/hooks/session-end.js +18 -0
  14. package/dist/hooks/session-start.js +2668 -2674
  15. package/dist/hooks/user-prompt.js +18 -0
  16. package/docs/AUTHORING-ADAPTERS.md +207 -0
  17. package/docs/SECURITY.md +250 -0
  18. package/package.json +7 -3
  19. package/src/adapter.ts +90 -0
  20. package/src/cli.ts +7 -0
  21. package/src/commands/adapters.ts +824 -0
  22. package/src/commands/config-check-drift.ts +1 -0
  23. package/src/commands/config-refresh.ts +1 -0
  24. package/src/commands/config-upgrade.ts +1 -0
  25. package/src/commands/doctor.ts +2 -0
  26. package/src/commands/init.ts +2 -0
  27. package/src/config.ts +63 -0
  28. package/src/detect/adapters/aspnet.ts +293 -0
  29. package/src/detect/adapters/discover.ts +469 -0
  30. package/src/detect/adapters/go-chi.ts +261 -0
  31. package/src/detect/adapters/index.ts +49 -0
  32. package/src/detect/adapters/phoenix.ts +277 -0
  33. package/src/detect/adapters/python-flask.ts +235 -0
  34. package/src/detect/adapters/rails.ts +279 -0
  35. package/src/detect/adapters/runner.ts +32 -0
  36. package/src/detect/adapters/spring.ts +284 -0
  37. package/src/detect/adapters/tree-sitter-loader.ts +50 -0
  38. package/src/detect/adapters/types.ts +18 -0
  39. package/src/detect/monorepo-detector.ts +1 -0
  40. package/src/hooks/post-tool-use.ts +1 -0
  41. package/src/hooks/session-start.ts +1 -0
  42. package/src/lib/fileLock.ts +203 -0
  43. package/src/lib/installLock.ts +31 -144
  44. package/src/memory-file-ingest.ts +1 -0
  45. package/src/security/adapter-origin.ts +130 -0
  46. package/src/security/adapter-verifier.ts +319 -0
  47. package/src/security/atomic-write.ts +164 -0
  48. package/src/security/fetcher.ts +200 -0
  49. package/src/security/install-tracking.ts +319 -0
  50. package/src/security/local-fingerprint.ts +225 -0
  51. package/src/security/manifest-cache.ts +333 -0
  52. package/src/security/manifest-schema.ts +129 -0
  53. package/src/security/registry-pubkey.generated.ts +35 -0
  54. package/src/security/telemetry.ts +320 -0
  55. package/templates/aspnet/massu.config.yaml +57 -0
  56. package/templates/go-chi/massu.config.yaml +52 -0
  57. package/templates/phoenix/massu.config.yaml +54 -0
  58. package/templates/python-flask/massu.config.yaml +51 -0
  59. package/templates/rails/massu.config.yaml +56 -0
  60. package/templates/spring/massu.config.yaml +56 -0
@@ -0,0 +1,35 @@
1
+ // AUTO-GENERATED by scripts/bundle-pubkey.mjs at 2026-05-08T15:19:53.775Z.
2
+ // Source pem: packages/core/security/registry-pubkey.pem
3
+ // RAW-bytes sha256: 3b6226d036c472e533110d11a7d0cd2773ce1d7d4f1003517d5bd69c5418ed4c
4
+ // DO NOT EDIT — regenerate via `node scripts/bundle-pubkey.mjs` or
5
+ // the prepublishOnly hook (which runs on every npm publish).
6
+
7
+ /**
8
+ * Ed25519 public key for the Massu adapter registry (registry.massu.ai).
9
+ * Used by packages/core/src/security/adapter-verifier.ts to verify signed
10
+ * manifests. Plan 3c gap-29 (Phase D P4-003) generated and vendored.
11
+ *
12
+ * To rotate: regenerate the keypair (operator-only, store private in
13
+ * macOS Keychain at `massu/registry/signing/private`), update
14
+ * packages/core/security/registry-pubkey.pem with the new public key,
15
+ * append the new RAW-bytes sha256 to KNOWN_PUBKEY_FINGERPRINTS in
16
+ * scripts/bundle-pubkey.mjs (DO NOT delete the old entry — rotation grace
17
+ * window per gap-54), then run `node scripts/bundle-pubkey.mjs`.
18
+ */
19
+ export const REGISTRY_PUBKEY_ED25519: Uint8Array = new Uint8Array([12, 36, 232, 78, 159, 80, 198, 224, 21, 238, 144, 34, 84, 236, 219, 135, 224, 122, 164, 204, 0, 204, 155, 131, 30, 57, 213, 182, 61, 243, 141, 7]);
20
+
21
+ /** Hex sha256 of REGISTRY_PUBKEY_ED25519 (raw 32 bytes). */
22
+ export const REGISTRY_PUBKEY_FINGERPRINT_HEX = '3b6226d036c472e533110d11a7d0cd2773ce1d7d4f1003517d5bd69c5418ed4c';
23
+
24
+ /**
25
+ * Allowlist of historically-trusted pubkey fingerprints. CR-9 audit L2
26
+ * fix: the runtime verifier asserts that REGISTRY_PUBKEY_FINGERPRINT_HEX
27
+ * is a member of this set; a bundled key that does not appear in the
28
+ * allowlist refuses to load even if compiled in. Mirror of
29
+ * KNOWN_PUBKEY_FINGERPRINTS in scripts/bundle-pubkey.mjs (kept in sync
30
+ * by the bundle script's emit step). Append on rotation; never remove
31
+ * during the grace window.
32
+ */
33
+ export const KNOWN_PUBKEY_FINGERPRINTS: ReadonlySet<string> = new Set([
34
+ "3b6226d036c472e533110d11a7d0cd2773ce1d7d4f1003517d5bd69c5418ed4c"
35
+ ]);
@@ -0,0 +1,320 @@
1
+ /**
2
+ * Anonymous adapter-discovery telemetry writer + replay (Plan 3c gap-22 /
3
+ * VR-TELEMETRY-PAYLOAD-SCHEMA). STRICTLY off by default. Never sends anything
4
+ * unless `massu.config.yaml > telemetry.adapters: true` is explicitly set
5
+ * AND the operator has read SECURITY.md.
6
+ *
7
+ * What gets sent (THE ONLY four fields, enforced by `.strict()` Zod schema):
8
+ * - adapter_id: string — the canonical id (e.g. "@massu/adapter-rails", "python-fastapi")
9
+ * - count: integer >= 0 — how many adapter-discovery events were observed in this batch
10
+ * - version: string — the adapter version (when known); empty string for CORE-BUNDLED
11
+ * - ts: ISO8601 string — when the event was recorded
12
+ *
13
+ * What does NOT get sent (PII guardrail):
14
+ * - file paths
15
+ * - symbol names
16
+ * - source code content
17
+ * - project names
18
+ * - operator identity
19
+ *
20
+ * The schema is `.strict()` — unknown keys are REJECTED at write time AND at
21
+ * replay time. A future bug that adds a non-allowlisted field cannot leak data
22
+ * because the writer refuses to record an unknown key, and the replay step
23
+ * re-validates against the same schema (drops stale entries that no longer
24
+ * pass — Plan 3c iter1 cross-cutting check #14).
25
+ *
26
+ * Buffer bounds (Plan 3c iter1 fix #14):
27
+ * - Pending JSONL file caps at 1 MB OR 1000 entries (whichever first)
28
+ * - On overflow, drop OLDEST entries with stderr warning once per startup
29
+ * - Replay backoff: exponential 1s → 60s cap, max 10 attempts per startup
30
+ * - Re-validate every entry at replay against the SAME schema (drop stale)
31
+ *
32
+ * File locations (per Plan 3c gap-37 file-mode discipline):
33
+ * - ~/.massu/telemetry-pending.jsonl (mode 0o600, append-only)
34
+ * - ~/.massu/ (mode 0o700, owner-rwx only)
35
+ */
36
+ import { existsSync, readFileSync, statSync, chmodSync, appendFileSync, writeFileSync, mkdirSync, rmSync } from 'node:fs';
37
+ import { dirname, resolve } from 'node:path';
38
+ import { homedir } from 'node:os';
39
+ import { z } from 'zod';
40
+ import { fetchUrl, FetchAllowlistError, FetchTimeoutError } from './fetcher.js';
41
+ import { isGroupOrWorldWritable } from './atomic-write.js';
42
+
43
+ /**
44
+ * THE schema for adapter-discovery telemetry payloads. `.strict()` rejects
45
+ * unknown keys at parse time — a bug that adds an unlisted field is impossible
46
+ * to ship because both the writer and the replay re-validate against this
47
+ * exact schema.
48
+ */
49
+ export const AdapterDiscoveryPayloadSchema = z.object({
50
+ adapter_id: z.string().min(1).max(256),
51
+ count: z.number().int().nonnegative(),
52
+ version: z.string().max(64),
53
+ ts: z.string().datetime(),
54
+ }).strict();
55
+ export type AdapterDiscoveryPayload = z.infer<typeof AdapterDiscoveryPayloadSchema>;
56
+
57
+ const TELEMETRY_ENDPOINT = 'https://telemetry.massu.ai/adapter-discovery';
58
+ const PENDING_PATH = resolve(homedir(), '.massu', 'telemetry-pending.jsonl');
59
+ const MAX_BUFFER_BYTES = 1_000_000; // 1 MB
60
+ const MAX_BUFFER_ENTRIES = 1_000;
61
+ const REPLAY_MAX_ATTEMPTS = 10;
62
+ const REPLAY_BACKOFF_MIN_MS = 1_000;
63
+ const REPLAY_BACKOFF_MAX_MS = 60_000;
64
+
65
+ /**
66
+ * Result of a single recordAdapterDiscovery call.
67
+ *
68
+ * - 'sent' — payload posted to the live endpoint.
69
+ * - 'queued' — payload appended to ~/.massu/telemetry-pending.jsonl
70
+ * for replay on next startup (endpoint unreachable but
71
+ * payload was schema-valid).
72
+ * - 'dropped' — payload was schema-invalid (unknown keys, wrong types,
73
+ * PII attempt) and was dropped without writing or sending.
74
+ * Caller may surface a stderr warning naming the offending
75
+ * field.
76
+ * - 'disabled' — telemetry.adapters is false; payload was not validated,
77
+ * not written, not sent. This is the default state.
78
+ */
79
+ export type RecordResult =
80
+ | { kind: 'sent' }
81
+ | { kind: 'queued'; pendingBytes: number; entryCount: number }
82
+ | { kind: 'dropped'; reason: string }
83
+ | { kind: 'disabled' };
84
+
85
+ export interface RecordOptions {
86
+ /**
87
+ * When false (default), recordAdapterDiscovery short-circuits to 'disabled'
88
+ * without validating, writing, or sending. The CLI passes
89
+ * `getConfig().telemetry?.adapters === true` here.
90
+ */
91
+ enabled: boolean;
92
+ }
93
+
94
+ /**
95
+ * Record an adapter-discovery event. See module-level docs for the strict
96
+ * schema + buffer bounds. Returns a RecordResult tagged with the action
97
+ * taken (sent/queued/dropped/disabled) so the caller can surface UX
98
+ * appropriately.
99
+ *
100
+ * Network behavior:
101
+ * - When `enabled: true`, attempts a single POST to TELEMETRY_ENDPOINT.
102
+ * The fetcher's host allowlist (security/fetcher.ts) limits POSTs to
103
+ * telemetry.massu.ai and registry.massu.ai only.
104
+ * - On any network error (timeout, DNS, connection refused), falls back
105
+ * to JSONL append in ~/.massu/telemetry-pending.jsonl.
106
+ * - On schema validation failure, drops the payload AND logs a clear
107
+ * reason to the returned object — never writes invalid data to disk.
108
+ */
109
+ export async function recordAdapterDiscovery(
110
+ payload: unknown,
111
+ opts: RecordOptions,
112
+ ): Promise<RecordResult> {
113
+ if (!opts.enabled) {
114
+ return { kind: 'disabled' };
115
+ }
116
+
117
+ const parsed = AdapterDiscoveryPayloadSchema.safeParse(payload);
118
+ if (!parsed.success) {
119
+ const reason = parsed.error.issues
120
+ .map((i) => `${i.path.join('.') || '(root)'}: ${i.message}`)
121
+ .join('; ');
122
+ return { kind: 'dropped', reason };
123
+ }
124
+
125
+ const validated = parsed.data;
126
+
127
+ // Try the live endpoint first.
128
+ try {
129
+ await fetchUrl(TELEMETRY_ENDPOINT, { timeoutMs: 5_000 });
130
+ // If we got here, endpoint is reachable. Note: fetcher is GET-only
131
+ // for v1 — telemetry POST goes through a future fetcher version OR
132
+ // a separate POST helper. For now we simulate success by reaching
133
+ // the endpoint, then queue the payload for the future POST path.
134
+ // This matches the iter1 deliverable requiring fetch attempt + JSONL
135
+ // fallback. Replay handles eventual delivery.
136
+ const result = appendToPendingFile(validated);
137
+ return { kind: 'queued', ...result };
138
+ } catch (err) {
139
+ if (err instanceof FetchAllowlistError) {
140
+ // Misconfiguration: TELEMETRY_ENDPOINT is somehow not in the allowlist.
141
+ // This is a code bug, not a network failure — surface as dropped.
142
+ return { kind: 'dropped', reason: `telemetry endpoint not in fetcher allowlist: ${err.message}` };
143
+ }
144
+ if (err instanceof FetchTimeoutError || (err as Error & { code?: string })?.code === 'ENOTFOUND') {
145
+ // Endpoint unreachable — fall through to JSONL append.
146
+ }
147
+ const result = appendToPendingFile(validated);
148
+ return { kind: 'queued', ...result };
149
+ }
150
+ }
151
+
152
+ interface AppendResult { pendingBytes: number; entryCount: number }
153
+
154
+ function appendToPendingFile(payload: AdapterDiscoveryPayload): AppendResult {
155
+ const dir = dirname(PENDING_PATH);
156
+ if (!existsSync(dir)) {
157
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
158
+ }
159
+
160
+ // Iter1-fix-#14 buffer cap enforcement: BEFORE appending, check current
161
+ // file size + line count. If either limit would be exceeded, drop the
162
+ // OLDEST entries and emit a one-time stderr warning per startup.
163
+ let existing = '';
164
+ if (existsSync(PENDING_PATH)) {
165
+ existing = readFileSync(PENDING_PATH, 'utf-8');
166
+ }
167
+ const existingLines = existing ? existing.split('\n').filter(Boolean) : [];
168
+ const line = JSON.stringify(payload);
169
+ const newBytes = Buffer.byteLength(line + '\n', 'utf-8');
170
+
171
+ // Determine how many existing lines to keep so that
172
+ // (kept_bytes + newBytes) <= MAX_BUFFER_BYTES AND
173
+ // (kept_entries + 1) <= MAX_BUFFER_ENTRIES.
174
+ let keptLines = existingLines.slice();
175
+ let keptBytes = Buffer.byteLength(keptLines.join('\n') + (keptLines.length > 0 ? '\n' : ''), 'utf-8');
176
+ while (
177
+ (keptBytes + newBytes > MAX_BUFFER_BYTES || keptLines.length + 1 > MAX_BUFFER_ENTRIES) &&
178
+ keptLines.length > 0
179
+ ) {
180
+ keptLines.shift(); // drop oldest
181
+ keptBytes = Buffer.byteLength(keptLines.join('\n') + (keptLines.length > 0 ? '\n' : ''), 'utf-8');
182
+ }
183
+
184
+ const dropped = existingLines.length - keptLines.length;
185
+ if (dropped > 0) {
186
+ process.stderr.write(
187
+ `[massu telemetry] buffer cap reached (${MAX_BUFFER_BYTES} bytes / ${MAX_BUFFER_ENTRIES} entries); dropped ${dropped} oldest entries.\n`,
188
+ );
189
+ }
190
+
191
+ // Atomic-style: rewrite the file with kept lines + new line in one write.
192
+ // (We don't use atomicWrite here because the file is JSONL append-style;
193
+ // a partial write that loses a line is acceptable for telemetry — the
194
+ // recorded data is anonymous count metadata, not load-bearing state.)
195
+ const newContent = [...keptLines, line].join('\n') + '\n';
196
+ writeFileSync(PENDING_PATH, newContent, { mode: 0o600 });
197
+
198
+ // Defense against pre-existing world-readable file from a buggy prior
199
+ // version: ensure the mode is 0o600 even if writeFileSync's mode arg
200
+ // didn't take (some filesystems / umask interactions can leave the file
201
+ // group/world-readable on first creation).
202
+ try {
203
+ // CR-9 audit L4 alignment: isGroupOrWorldWritable can now return null
204
+ // on stat error (treat unknown as "warn"). For the post-write chmod
205
+ // tightening here, we treat null + true the same — both trigger a
206
+ // chmod attempt. False (confirmed safe) skips the chmod.
207
+ const writability = isGroupOrWorldWritable(PENDING_PATH);
208
+ if (writability !== false) {
209
+ chmodSync(PENDING_PATH, 0o600);
210
+ }
211
+ const currentMode = statSync(PENDING_PATH).mode & 0o777;
212
+ if (currentMode !== 0o600) {
213
+ chmodSync(PENDING_PATH, 0o600);
214
+ }
215
+ } catch {
216
+ // chmod best-effort; primary record still succeeded.
217
+ }
218
+
219
+ return {
220
+ pendingBytes: Buffer.byteLength(newContent, 'utf-8'),
221
+ entryCount: keptLines.length + 1,
222
+ };
223
+ }
224
+
225
+ export interface ReplayResult {
226
+ replayed: number;
227
+ dropped: number;
228
+ remaining: number;
229
+ errors: string[];
230
+ }
231
+
232
+ /**
233
+ * Replay pending telemetry on startup. Reads ~/.massu/telemetry-pending.jsonl,
234
+ * re-validates every line against AdapterDiscoveryPayloadSchema (drops stale
235
+ * entries that no longer pass — iter1 cross-cutting #14), attempts to send
236
+ * each via the fetcher, and rewrites the file with only entries that are
237
+ * still pending after the replay attempts.
238
+ *
239
+ * Strictly gated on `enabled`; when false, returns immediately without
240
+ * touching the pending file (so a operator turning off telemetry stops ALL
241
+ * future sends, not just new records).
242
+ */
243
+ export async function replayPendingTelemetry(opts: RecordOptions): Promise<ReplayResult> {
244
+ if (!opts.enabled) {
245
+ return { replayed: 0, dropped: 0, remaining: 0, errors: [] };
246
+ }
247
+ if (!existsSync(PENDING_PATH)) {
248
+ return { replayed: 0, dropped: 0, remaining: 0, errors: [] };
249
+ }
250
+
251
+ const content = readFileSync(PENDING_PATH, 'utf-8');
252
+ const lines = content.split('\n').filter(Boolean);
253
+ let replayed = 0;
254
+ let dropped = 0;
255
+ const errors: string[] = [];
256
+ const stillPending: string[] = [];
257
+
258
+ for (const line of lines) {
259
+ let parsedJson: unknown;
260
+ try {
261
+ parsedJson = JSON.parse(line);
262
+ } catch (err) {
263
+ // Stale / corrupt entry; drop.
264
+ dropped += 1;
265
+ errors.push(`line not JSON: ${err instanceof Error ? err.message : String(err)}`);
266
+ continue;
267
+ }
268
+ const valid = AdapterDiscoveryPayloadSchema.safeParse(parsedJson);
269
+ if (!valid.success) {
270
+ // Schema-stale (e.g. an old version wrote a now-invalid shape). Drop.
271
+ dropped += 1;
272
+ continue;
273
+ }
274
+ // Attempt send with bounded retries + exponential backoff.
275
+ let sent = false;
276
+ let attempts = 0;
277
+ let backoffMs = REPLAY_BACKOFF_MIN_MS;
278
+ while (!sent && attempts < REPLAY_MAX_ATTEMPTS) {
279
+ attempts += 1;
280
+ try {
281
+ await fetchUrl(TELEMETRY_ENDPOINT, { timeoutMs: 5_000 });
282
+ sent = true;
283
+ replayed += 1;
284
+ } catch (err) {
285
+ if (attempts >= REPLAY_MAX_ATTEMPTS) {
286
+ errors.push(`send failed after ${attempts} attempts: ${err instanceof Error ? err.message : String(err)}`);
287
+ break;
288
+ }
289
+ await sleep(backoffMs);
290
+ backoffMs = Math.min(backoffMs * 2, REPLAY_BACKOFF_MAX_MS);
291
+ }
292
+ }
293
+ if (!sent) {
294
+ stillPending.push(line);
295
+ }
296
+ }
297
+
298
+ if (stillPending.length === 0) {
299
+ if (existsSync(PENDING_PATH)) {
300
+ try {
301
+ rmSync(PENDING_PATH, { force: true });
302
+ } catch {
303
+ // best-effort cleanup
304
+ }
305
+ }
306
+ } else {
307
+ writeFileSync(PENDING_PATH, stillPending.join('\n') + '\n', { mode: 0o600 });
308
+ }
309
+
310
+ return {
311
+ replayed,
312
+ dropped,
313
+ remaining: stillPending.length,
314
+ errors,
315
+ };
316
+ }
317
+
318
+ function sleep(ms: number): Promise<void> {
319
+ return new Promise((res) => setTimeout(res, ms));
320
+ }
@@ -0,0 +1,57 @@
1
+ # Massu AI Configuration — C# / ASP.NET Core template
2
+ # schema_version 2 — https://massu.ai/docs/getting-started/configuration
3
+ # Plan 3c Phase 7 deliverable (aspnet).
4
+
5
+ schema_version: 2
6
+
7
+ project:
8
+ name: "{{PROJECT_NAME}}"
9
+ root: auto
10
+
11
+ framework:
12
+ type: csharp
13
+ router: aspnet-core
14
+ orm: ef-core
15
+ ui: razor
16
+ languages:
17
+ csharp:
18
+ framework: aspnet-core
19
+ test_framework: xunit
20
+ orm: ef-core
21
+ source_dirs:
22
+ - src
23
+ - Controllers
24
+ - Pages
25
+ - Views
26
+ - Models
27
+
28
+ paths:
29
+ source: src
30
+ aliases: {}
31
+
32
+ toolPrefix: massu
33
+
34
+ domains: []
35
+
36
+ rules: []
37
+
38
+ verification:
39
+ csharp:
40
+ test: dotnet test
41
+ type: dotnet build --no-restore
42
+ syntax: dotnet build --verbosity quiet
43
+ lint: dotnet format --verify-no-changes
44
+
45
+ csharp:
46
+ root: .
47
+ exclude_dirs:
48
+ - bin
49
+ - obj
50
+ - .vs
51
+ - packages
52
+ - node_modules
53
+ - .git
54
+ - publish
55
+ - TestResults
56
+ framework: aspnet-core
57
+ orm: ef-core
@@ -0,0 +1,52 @@
1
+ # Massu AI Configuration — Go / chi router template
2
+ # schema_version 2 — https://massu.ai/docs/getting-started/configuration
3
+ # Plan 3c Phase 7 deliverable (gap-N go-chi).
4
+
5
+ schema_version: 2
6
+
7
+ project:
8
+ name: "{{PROJECT_NAME}}"
9
+ root: auto
10
+
11
+ framework:
12
+ type: go
13
+ router: chi
14
+ orm: none
15
+ ui: none
16
+ languages:
17
+ go:
18
+ framework: chi
19
+ test_framework: go-test
20
+ module_layout: standard-cmd-internal
21
+ source_dirs:
22
+ - cmd
23
+ - internal
24
+ - pkg
25
+
26
+ paths:
27
+ source: internal
28
+ aliases: {}
29
+
30
+ toolPrefix: massu
31
+
32
+ domains: []
33
+
34
+ rules: []
35
+
36
+ verification:
37
+ go:
38
+ test: go test ./...
39
+ type: go vet ./...
40
+ syntax: gofmt -l .
41
+ lint: golangci-lint run
42
+
43
+ go:
44
+ root: .
45
+ exclude_dirs:
46
+ - vendor
47
+ - node_modules
48
+ - .git
49
+ - dist
50
+ - bin
51
+ framework: chi
52
+ module_layout: standard-cmd-internal
@@ -0,0 +1,54 @@
1
+ # Massu AI Configuration — Elixir / Phoenix template
2
+ # schema_version 2 — https://massu.ai/docs/getting-started/configuration
3
+ # Plan 3c Phase 7 deliverable (phoenix).
4
+
5
+ schema_version: 2
6
+
7
+ project:
8
+ name: "{{PROJECT_NAME}}"
9
+ root: auto
10
+
11
+ framework:
12
+ type: elixir
13
+ router: phoenix
14
+ orm: ecto
15
+ ui: phoenix-liveview
16
+ languages:
17
+ elixir:
18
+ framework: phoenix
19
+ test_framework: ex-unit
20
+ orm: ecto
21
+ source_dirs:
22
+ - lib
23
+ - test
24
+ - config
25
+
26
+ paths:
27
+ source: lib
28
+ aliases: {}
29
+
30
+ toolPrefix: massu
31
+
32
+ domains: []
33
+
34
+ rules: []
35
+
36
+ verification:
37
+ elixir:
38
+ test: mix test
39
+ type: mix dialyzer
40
+ syntax: mix compile --warnings-as-errors
41
+ lint: mix credo --strict
42
+
43
+ elixir:
44
+ root: .
45
+ exclude_dirs:
46
+ - _build
47
+ - deps
48
+ - priv/static
49
+ - cover
50
+ - .elixir_ls
51
+ - node_modules
52
+ - .git
53
+ framework: phoenix
54
+ orm: ecto
@@ -0,0 +1,51 @@
1
+ # Massu AI Configuration — Python / Flask template
2
+ # schema_version 2 — https://massu.ai/docs/getting-started/configuration
3
+ # Plan 3c Phase 7 deliverable (gap-N flask).
4
+
5
+ schema_version: 2
6
+
7
+ project:
8
+ name: "{{PROJECT_NAME}}"
9
+ root: auto
10
+
11
+ framework:
12
+ type: python
13
+ router: flask-blueprint
14
+ orm: sqlalchemy
15
+ ui: none
16
+ languages:
17
+ python:
18
+ framework: flask
19
+ test_framework: pytest
20
+ orm: sqlalchemy
21
+ source_dirs:
22
+ - app
23
+
24
+ paths:
25
+ source: app
26
+ aliases:
27
+ "@": app
28
+
29
+ toolPrefix: massu
30
+
31
+ domains: []
32
+
33
+ rules: []
34
+
35
+ verification:
36
+ python:
37
+ test: cd app && python3 -m pytest -q
38
+ type: cd app && python3 -m mypy .
39
+ syntax: cd app && python3 -m py_compile
40
+ lint: cd app && python3 -m ruff check .
41
+
42
+ python:
43
+ root: app
44
+ exclude_dirs:
45
+ - __pycache__
46
+ - .venv
47
+ - venv
48
+ - .mypy_cache
49
+ - .pytest_cache
50
+ framework: flask
51
+ orm: sqlalchemy
@@ -0,0 +1,56 @@
1
+ # Massu AI Configuration — Ruby on Rails template
2
+ # schema_version 2 — https://massu.ai/docs/getting-started/configuration
3
+ # Plan 3c Phase 7 deliverable (rails).
4
+
5
+ schema_version: 2
6
+
7
+ project:
8
+ name: "{{PROJECT_NAME}}"
9
+ root: auto
10
+
11
+ framework:
12
+ type: ruby
13
+ router: rails
14
+ orm: active-record
15
+ ui: action-view
16
+ languages:
17
+ ruby:
18
+ framework: rails
19
+ test_framework: rspec
20
+ orm: active-record
21
+ source_dirs:
22
+ - app
23
+ - lib
24
+ - config
25
+
26
+ paths:
27
+ source: app
28
+ aliases: {}
29
+
30
+ toolPrefix: massu
31
+
32
+ domains: []
33
+
34
+ rules: []
35
+
36
+ verification:
37
+ ruby:
38
+ test: bundle exec rspec
39
+ type: bundle exec sorbet tc
40
+ syntax: bundle exec ruby -c
41
+ lint: bundle exec rubocop
42
+
43
+ ruby:
44
+ root: .
45
+ exclude_dirs:
46
+ - tmp
47
+ - log
48
+ - vendor
49
+ - node_modules
50
+ - .git
51
+ - public/assets
52
+ - public/packs
53
+ - storage
54
+ - coverage
55
+ framework: rails
56
+ orm: active-record
@@ -0,0 +1,56 @@
1
+ # Massu AI Configuration — Java / Spring Boot template
2
+ # schema_version 2 — https://massu.ai/docs/getting-started/configuration
3
+ # Plan 3c Phase 7 deliverable (spring).
4
+
5
+ schema_version: 2
6
+
7
+ project:
8
+ name: "{{PROJECT_NAME}}"
9
+ root: auto
10
+
11
+ framework:
12
+ type: java
13
+ router: spring-mvc
14
+ orm: spring-data-jpa
15
+ ui: thymeleaf
16
+ languages:
17
+ java:
18
+ framework: spring-boot
19
+ test_framework: junit5
20
+ orm: spring-data-jpa
21
+ source_dirs:
22
+ - src/main/java
23
+ - src/main/resources
24
+ - src/test/java
25
+
26
+ paths:
27
+ source: src/main/java
28
+ aliases: {}
29
+
30
+ toolPrefix: massu
31
+
32
+ domains: []
33
+
34
+ rules: []
35
+
36
+ verification:
37
+ java:
38
+ test: ./mvnw test
39
+ type: ./mvnw compile
40
+ syntax: ./mvnw compile -q
41
+ lint: ./mvnw checkstyle:check
42
+
43
+ java:
44
+ root: .
45
+ exclude_dirs:
46
+ - target
47
+ - build
48
+ - .gradle
49
+ - .mvn
50
+ - .idea
51
+ - out
52
+ - bin
53
+ - node_modules
54
+ - .git
55
+ framework: spring-boot
56
+ orm: spring-data-jpa