@ijfw/memory-server 1.4.4 → 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.
- package/fixtures/truncation-corpus/_generate-corpus.js +367 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-01/events.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-01/intent-journal.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-01/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-01/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-02/events.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-02/intent-journal.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-02/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-02/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-03/events.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-03/intent-journal.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-03/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-03/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-04/events.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-04/intent-journal.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-04/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-04/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-05/events.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-05/intent-journal.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-05/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-05/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-01/events.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-01/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-01/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-01/snapshots/v-midO-1-advance.json +11 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-01/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-02/events.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-02/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-02/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-02/snapshots/v-midO-2-advance.json +11 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-02/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-03/events.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-03/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-03/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-03/snapshots/v-midO-3-advance.json +11 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-03/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-04/events.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-04/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-04/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-04/snapshots/v-midO-4-advance.json +11 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-04/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-05/events.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-05/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-05/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-05/snapshots/v-midO-5-advance.json +11 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-05/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-01/events.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-01/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-01/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-01/target/.ijfw/blackboard/decisions.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-02/events.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-02/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-02/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-02/target/.ijfw/blackboard/decisions.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-03/events.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-03/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-03/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-03/target/.ijfw/blackboard/decisions.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-04/events.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-04/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-04/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-04/target/.ijfw/blackboard/decisions.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-05/events.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-05/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-05/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-05/target/.ijfw/blackboard/decisions.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-04-no-events-01/events.jsonl +0 -0
- package/fixtures/truncation-corpus/fx-04-no-events-01/intent-journal.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-04-no-events-01/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-04-no-events-01/snapshots/v-noEv-1-set-phase.json +11 -0
- package/fixtures/truncation-corpus/fx-04-no-events-01/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-04-no-events-02/events.jsonl +0 -0
- package/fixtures/truncation-corpus/fx-04-no-events-02/intent-journal.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-04-no-events-02/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-04-no-events-02/snapshots/v-noEv-2-set-phase.json +11 -0
- package/fixtures/truncation-corpus/fx-04-no-events-02/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-04-no-events-03/events.jsonl +0 -0
- package/fixtures/truncation-corpus/fx-04-no-events-03/intent-journal.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-04-no-events-03/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-04-no-events-03/snapshots/v-noEv-3-set-phase.json +11 -0
- package/fixtures/truncation-corpus/fx-04-no-events-03/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-04-no-events-04/events.jsonl +0 -0
- package/fixtures/truncation-corpus/fx-04-no-events-04/intent-journal.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-04-no-events-04/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-04-no-events-04/snapshots/v-noEv-4-set-phase.json +11 -0
- package/fixtures/truncation-corpus/fx-04-no-events-04/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-04-no-events-05/events.jsonl +0 -0
- package/fixtures/truncation-corpus/fx-04-no-events-05/intent-journal.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-04-no-events-05/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-04-no-events-05/snapshots/v-noEv-5-set-phase.json +11 -0
- package/fixtures/truncation-corpus/fx-04-no-events-05/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-01/events.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-01/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-01/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-01/snapshots/v-errT-1-partial.json +11 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-01/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-02/events.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-02/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-02/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-02/target/.ijfw/blackboard/decisions.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-03/events.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-03/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-03/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-03/snapshots/v-errT-3-partial.json +11 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-03/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-04/events.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-04/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-04/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-04/target/.ijfw/blackboard/decisions.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-05/events.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-05/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-05/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-05/snapshots/v-errT-5-partial.json +11 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-05/target/.ijfw/state/workflow.json +1 -0
- package/package.json +1 -1
- package/src/active-extension-writer.js +144 -64
- package/src/api-client.js +43 -5
- package/src/audit-roster.js +80 -5
- package/src/blackboard.js +298 -6
- package/src/cli-run.js +33 -5
- package/src/codex-agents.js +96 -5
- package/src/cost/aggregator.js +39 -9
- package/src/cost/pricing.js +57 -0
- package/src/cost/readers/gemini.js +1 -1
- package/src/cross-audit-chunker.js +189 -0
- package/src/cross-dispatcher.js +124 -21
- package/src/cross-orchestrator-cli.js +550 -14
- package/src/cross-orchestrator.js +1016 -17
- package/src/cross-project-search.js +195 -9
- package/src/dashboard-client-waves.html +304 -0
- package/src/dashboard-client.html +5 -1
- package/src/dashboard-server.js +73 -0
- package/src/deploy-alerts.js +150 -0
- package/src/design/iframe-bridge.js +242 -0
- package/src/design-companion.js +144 -0
- package/src/dispatch/checkpoint-cli.js +97 -0
- package/src/dispatch/colon-syntax.js +81 -1
- package/src/dispatch/extension.js +26 -2
- package/src/dispatch/registry-cli.js +4 -1
- package/src/dispatch/wave-cli.js +201 -6
- package/src/dispatch/worktree-cli.js +40 -0
- package/src/dispatch-planner.js +97 -2
- package/src/dream/runner.mjs +47 -11
- package/src/dream/stage-runner.js +40 -0
- package/src/dream/state-file.js +102 -0
- package/src/extension-installer.js +70 -24
- package/src/extension-quota-tracker.js +4 -2
- package/src/extension-registry.js +289 -35
- package/src/feedback-detector.js +26 -0
- package/src/fs-lock.js +259 -7
- package/src/gate-result.js +95 -1
- package/src/hero-line.js +86 -5
- package/src/intent-router.js +35 -0
- package/src/lib/a11y-contract.js +117 -0
- package/src/lib/atomic-io.js +29 -8
- package/src/lib/cache-keepalive.js +150 -0
- package/src/lib/jsonl-rotation.js +104 -0
- package/src/lib/lighthouse-pillar.js +121 -0
- package/src/lib/llm-call.js +121 -0
- package/src/lib/playwright-baseline.js +205 -0
- package/src/lib/rekor-bridge.js +221 -0
- package/src/lib/repo-map.js +392 -0
- package/src/lib/shasum-verify.js +164 -0
- package/src/lib/sketches-gc.js +132 -0
- package/src/lib/tmp-suffix.js +62 -0
- package/src/lib/ui-review-runner.js +554 -0
- package/src/lib/uispec-drift.js +301 -0
- package/src/lib/uispec-intake.js +381 -0
- package/src/lib/worktree-guards.js +118 -0
- package/src/lib/worktree-recovery.js +100 -0
- package/src/memory/auto-linker.js +152 -0
- package/src/memory/benchmark.js +498 -0
- package/src/memory/dedup.js +126 -0
- package/src/memory/embedding-cache.js +136 -0
- package/src/memory/fact-extractor.js +168 -0
- package/src/memory/fts5.js +65 -1
- package/src/memory/migrations/004-bitemporal.js +91 -0
- package/src/memory/migrations/005-vector-cache.js +61 -0
- package/src/memory/migrations/006-obsidian-graph.js +46 -0
- package/src/memory/migrations/007-skill-telemetry.js +24 -0
- package/src/memory/migrations/008-write-provenance.js +41 -0
- package/src/memory/obsidian-parser.js +91 -0
- package/src/memory/query-dataview.js +86 -0
- package/src/memory/search.js +10 -0
- package/src/memory/temporal.js +529 -0
- package/src/memory/tokenize.js +10 -0
- package/src/memory-facts-handler.js +37 -0
- package/src/memory-feedback.js +260 -2
- package/src/model-refresh.js +292 -0
- package/src/observability/cost-anomaly.js +166 -0
- package/src/observability/evaluator-checkpoint-contract.js +117 -0
- package/src/observability/trace-id.js +163 -0
- package/src/orchestrator/agents-md-blackboard.js +152 -0
- package/src/orchestrator/checkpoint-contract.md +140 -0
- package/src/orchestrator/debug-trident.js +570 -0
- package/src/orchestrator/merge-block-aware.js +350 -0
- package/src/orchestrator/plan-checker.js +475 -0
- package/src/orchestrator/post-done-runner.js +249 -0
- package/src/orchestrator/review.js +38 -3
- package/src/orchestrator/runtime-loop.js +430 -0
- package/src/orchestrator/skill-telemetry-sink.js +29 -0
- package/src/orchestrator/skill-telemetry.js +37 -0
- package/src/orchestrator/state-events.js +459 -0
- package/src/orchestrator/state-sdk.js +1764 -0
- package/src/orchestrator/status-protocol.js +84 -17
- package/src/orchestrator/subagent-telemetry.js +452 -0
- package/src/orchestrator/termination.js +160 -0
- package/src/orchestrator/verification-gate.js +200 -16
- package/src/orchestrator/wave-state.js +332 -23
- package/src/orchestrator/worktree-provision.js +77 -0
- package/src/override-use-registry.js +111 -5
- package/src/receipts.js +36 -4
- package/src/recovery/checkpoint.js +56 -3
- package/src/recovery/code-fixer.js +656 -0
- package/src/recovery/truncation.js +317 -0
- package/src/redactor.js +75 -6
- package/src/runtime-mediator.js +15 -0
- package/src/sanitizer.js +10 -0
- package/src/search-hybrid.js +139 -0
- package/src/server.js +603 -59
- package/src/swarm/worktree.js +27 -4
- package/src/swarm-config.js +94 -17
- package/src/team/domain-templates/book.json +51 -0
- package/src/team/domain-templates/business.json +41 -0
- package/src/team/domain-templates/content.json +50 -0
- package/src/team/domain-templates/design.json +44 -0
- package/src/team/domain-templates/research.json +41 -0
- package/src/team/domain-templates/software.json +40 -0
- package/src/team/generator.js +278 -3
- package/src/team/modify.js +203 -0
- package/src/team/schemas.js +48 -0
- package/src/update-apply.js +19 -3
package/src/swarm/worktree.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { existsSync, mkdirSync, realpathSync, rmSync } from 'node:fs';
|
|
8
8
|
import { isAbsolute, join, relative, resolve } from 'node:path';
|
|
9
9
|
import { spawnSync } from 'node:child_process';
|
|
10
|
+
import { createHash } from 'node:crypto';
|
|
10
11
|
import { appendBlackboardEvent, listBlackboardTasks, updateBlackboardTask } from '../blackboard.js';
|
|
11
12
|
import { createCheckpoint } from '../recovery/checkpoint.js';
|
|
12
13
|
|
|
@@ -29,12 +30,31 @@ export function createTaskWorktree(projectRoot, taskId, options = {}) {
|
|
|
29
30
|
|
|
30
31
|
const baseRef = options.baseRef || 'HEAD';
|
|
31
32
|
const worktreeDir = join(root, '.ijfw', 'worktrees');
|
|
32
|
-
const worktreePath = join(worktreeDir, safeTaskId(taskId));
|
|
33
|
-
if (existsSync(worktreePath)) return { ok: false, error: 'worktree-exists', path: worktreePath };
|
|
34
33
|
mkdirSync(worktreeDir, { recursive: true, mode: 0o700 });
|
|
35
34
|
|
|
35
|
+
// v1.5.0 audit-LOW-teams-#15: safeTaskId collision detection.
|
|
36
|
+
// Two distinct taskIds that normalize to the same safe id (e.g. `task:1`
|
|
37
|
+
// and `task/1` both become `task-1`) would otherwise collide on the same
|
|
38
|
+
// worktree path AND the same branch name. Detect the collision by checking
|
|
39
|
+
// BOTH the worktree dir AND the existing branch list; if either is taken,
|
|
40
|
+
// suffix the safe id with a short content hash of the original taskId so
|
|
41
|
+
// distinct tasks get distinct worktrees + branches.
|
|
42
|
+
const baseSafeId = safeTaskId(taskId);
|
|
43
|
+
let safeId = baseSafeId;
|
|
44
|
+
let candidatePath = join(worktreeDir, safeId);
|
|
45
|
+
let candidateBranch = options.branch || `ijfw/${safeId}`;
|
|
46
|
+
const branchExists = (name) => git(root, ['show-ref', '--verify', '--quiet', `refs/heads/${name}`]).status === 0;
|
|
47
|
+
if (!options.branch && (existsSync(candidatePath) || branchExists(candidateBranch))) {
|
|
48
|
+
const suffix = createHash('sha256').update(String(taskId)).digest('hex').slice(0, 7);
|
|
49
|
+
safeId = `${baseSafeId}-${suffix}`;
|
|
50
|
+
candidatePath = join(worktreeDir, safeId);
|
|
51
|
+
candidateBranch = `ijfw/${safeId}`;
|
|
52
|
+
}
|
|
53
|
+
const worktreePath = candidatePath;
|
|
54
|
+
if (existsSync(worktreePath)) return { ok: false, error: 'worktree-exists', path: worktreePath };
|
|
55
|
+
|
|
36
56
|
createCheckpoint(root, 'before-worktree-create', { actor: 'ijfw', message: taskId });
|
|
37
|
-
const branch = options.branch ||
|
|
57
|
+
const branch = options.branch || candidateBranch;
|
|
38
58
|
const result = git(root, ['worktree', 'add', '-b', branch, worktreePath, baseRef]);
|
|
39
59
|
if (result.status !== 0) return { ok: false, error: 'worktree-add-failed', stderr: result.stderr, stdout: result.stdout };
|
|
40
60
|
|
|
@@ -81,7 +101,10 @@ export function integrateTaskWorktree(projectRoot, taskId, options = {}) {
|
|
|
81
101
|
if (wtDirty.stdout.trim()) return { ok: false, error: 'worktree-has-uncommitted-changes', detail: wtDirty.stdout.trim().split('\n') };
|
|
82
102
|
|
|
83
103
|
createCheckpoint(root, 'before-worktree-integrate', { actor: 'ijfw', message: taskId });
|
|
84
|
-
|
|
104
|
+
// v1.5.0 audit-LOW-teams-#14: tag merge commits with task-id so
|
|
105
|
+
// `git log --grep="ijfw merge: <task-id>"` recovers the merge boundary
|
|
106
|
+
// even after history rewrites lose the branch label.
|
|
107
|
+
const merge = git(root, ['merge', '--no-ff', '-m', `ijfw merge: ${taskId}`, validation.branch]);
|
|
85
108
|
if (merge.status !== 0) {
|
|
86
109
|
updateBlackboardTask(root, taskId, {
|
|
87
110
|
worktree: { ...wt, status: 'merge-blocked', last_error: merge.stderr || merge.stdout },
|
package/src/swarm-config.js
CHANGED
|
@@ -25,22 +25,92 @@ const BASE = [
|
|
|
25
25
|
const TESTS_SPECIALIST = { id: 'tests', role: 'Test coverage', agent_type: 'pr-test-analyzer' };
|
|
26
26
|
const TYPES_SPECIALIST = { id: 'types', role: 'Type invariants', agent_type: 'type-design-analyzer' };
|
|
27
27
|
|
|
28
|
-
// v1.4.4 N6 — 5 specialist agents addressing v1.4.3 build pain points
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
const
|
|
28
|
+
// v1.4.4 N6 — 5 specialist agents addressing v1.4.3 build pain points.
|
|
29
|
+
// `since` is the version the specialist was introduced (lock-in #40 added
|
|
30
|
+
// retroactively in v1.5.0 W11-D1 so registration-history queries can sort
|
|
31
|
+
// by introduction).
|
|
32
|
+
const DOC_VERIFIER = { id: 'doc-verifier', role: 'Doc accuracy', agent_type: 'ijfw-doc-verifier', since: '1.4.4' };
|
|
33
|
+
const PATTERN_MAPPER = { id: 'pattern-mapper', role: 'Onboarding patterns', agent_type: 'ijfw-pattern-mapper', since: '1.4.4' };
|
|
34
|
+
const SECURITY_AUDITOR = { id: 'security-auditor', role: 'Security mitigations', agent_type: 'ijfw-security-auditor', since: '1.4.4' };
|
|
35
|
+
const INTEGRATION_CHECKER = { id: 'integration-checker', role: 'E2E flow verification', agent_type: 'ijfw-integration-checker', since: '1.4.4' };
|
|
36
|
+
const NYQUIST_AUDITOR = { id: 'nyquist-auditor', role: 'Coverage gaps', agent_type: 'ijfw-nyquist-auditor', since: '1.4.4' };
|
|
37
|
+
|
|
38
|
+
// v1.5.0 W11-D1 S9 — 5 NEW IJFW specialists addressing v1.4.4 build pain points.
|
|
39
|
+
const RALPH_LOOP_RUNNER = { id: 'ralph-loop-runner', role: 'Subagent truncation recovery', agent_type: 'ijfw-ralph-loop-runner', since: '1.5.0' };
|
|
40
|
+
const PLAN_CHECKER = { id: 'plan-checker', role: 'Plan vs codebase reality check', agent_type: 'ijfw-plan-checker', since: '1.5.0' };
|
|
41
|
+
const DEP_AUDIT = { id: 'dep-audit', role: 'Dependency / publishConfig drift', agent_type: 'ijfw-dep-audit', since: '1.5.0' };
|
|
42
|
+
const E2E_RUNNER = { id: 'e2e-runner', role: 'Pre-ship install/update/uninstall', agent_type: 'ijfw-e2e-runner', since: '1.5.0' };
|
|
43
|
+
const LLM_BUDGET_WATCHER = { id: 'llm-budget-watcher', role: 'Token-cost vs phase budget', agent_type: 'ijfw-llm-budget-watcher', since: '1.5.0' };
|
|
44
|
+
|
|
45
|
+
// v1.5.0 W11-D1 F1 — 3 GSD specialists folded in from R3 research.
|
|
46
|
+
const RELEASE_ENG = { id: 'release-eng', role: 'Release mechanics (version, tag, publish, rollback)', agent_type: 'ijfw-release-eng', since: '1.5.0' };
|
|
47
|
+
const DOC_WRITER = { id: 'doc-writer', role: 'CHANGELOG + README derivation', agent_type: 'ijfw-doc-writer', since: '1.5.0' };
|
|
48
|
+
const ACCESSIBILITY_ENG = { id: 'accessibility-eng', role: 'WCAG AA audit of frontend surfaces', agent_type: 'ijfw-accessibility-eng', since: '1.5.0' };
|
|
49
|
+
|
|
50
|
+
// All 8 v1.5.0 specialists, appended universally per W11-D1 spec.
|
|
51
|
+
const V150_SPECIALISTS = [
|
|
52
|
+
RALPH_LOOP_RUNNER, PLAN_CHECKER, DEP_AUDIT, E2E_RUNNER, LLM_BUDGET_WATCHER,
|
|
53
|
+
RELEASE_ENG, DOC_WRITER, ACCESSIBILITY_ENG,
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
// F-FUN-3 (audit-MED-teams-#6): non-software domain specialists. Mirrors
|
|
57
|
+
// the fixture role definitions so we don't duplicate prompts; the IDs here
|
|
58
|
+
// are the *bench* names the swarm config tracks. Selecting a non-software
|
|
59
|
+
// archetype yields a bench tailored to the domain (book gets story-architect,
|
|
60
|
+
// not accessibility-eng). Software remains the default to preserve back-compat.
|
|
61
|
+
const STORY_ARCHITECT = { id: 'story-architect', role: 'Plot + structure architecture', agent_type: 'ijfw-story-architect', since: '1.5.0' };
|
|
62
|
+
const CONTINUITY_EDITOR = { id: 'continuity-editor', role: 'Timeline + voice continuity', agent_type: 'ijfw-continuity-editor', since: '1.5.0' };
|
|
63
|
+
const PROSE_STYLIST = { id: 'prose-stylist', role: 'Sentence-level voice + pacing', agent_type: 'ijfw-prose-stylist', since: '1.5.0' };
|
|
64
|
+
|
|
65
|
+
const CAMPAIGN_STRATEGIST = { id: 'campaign-strategist', role: 'Audience + funnel strategy', agent_type: 'ijfw-campaign-strategist', since: '1.5.0' };
|
|
66
|
+
const COPY_EDITOR = { id: 'copy-editor', role: 'Channel-aware copy editing', agent_type: 'ijfw-copy-editor', since: '1.5.0' };
|
|
67
|
+
|
|
68
|
+
const RESEARCH_LEAD = { id: 'research-lead', role: 'Methodology + literature review', agent_type: 'ijfw-research-lead', since: '1.5.0' };
|
|
69
|
+
const DATA_ANALYST = { id: 'data-analyst', role: 'Quantitative analysis', agent_type: 'ijfw-data-analyst', since: '1.5.0' };
|
|
70
|
+
|
|
71
|
+
// Per-archetype bench definitions. Keys here track the project_archetypes
|
|
72
|
+
// vocabulary used by team/generator.js so a brief-detected archetype maps
|
|
73
|
+
// directly to the right bench without an extra translation layer.
|
|
74
|
+
const SOFTWARE_BENCH = [...BASE, TESTS_SPECIALIST, DOC_VERIFIER, PATTERN_MAPPER, SECURITY_AUDITOR, INTEGRATION_CHECKER, NYQUIST_AUDITOR, ...V150_SPECIALISTS];
|
|
75
|
+
const TYPED_BENCH = [...BASE, TESTS_SPECIALIST, TYPES_SPECIALIST, DOC_VERIFIER, PATTERN_MAPPER, SECURITY_AUDITOR, INTEGRATION_CHECKER, NYQUIST_AUDITOR, ...V150_SPECIALISTS];
|
|
76
|
+
const GO_RUST_BENCH = [...BASE, DOC_VERIFIER, PATTERN_MAPPER, SECURITY_AUDITOR, INTEGRATION_CHECKER, NYQUIST_AUDITOR, ...V150_SPECIALISTS];
|
|
77
|
+
const OTHER_BENCH = [...BASE, DOC_VERIFIER, PATTERN_MAPPER, SECURITY_AUDITOR, INTEGRATION_CHECKER, NYQUIST_AUDITOR, ...V150_SPECIALISTS];
|
|
78
|
+
|
|
79
|
+
const BOOK_BENCH = [STORY_ARCHITECT, CONTINUITY_EDITOR, PROSE_STYLIST, DOC_VERIFIER, NYQUIST_AUDITOR];
|
|
80
|
+
const CONTENT_BENCH = [CAMPAIGN_STRATEGIST, COPY_EDITOR, DOC_VERIFIER, NYQUIST_AUDITOR];
|
|
81
|
+
const RESEARCH_BENCH = [RESEARCH_LEAD, DATA_ANALYST, DOC_VERIFIER, NYQUIST_AUDITOR];
|
|
34
82
|
|
|
35
83
|
export const DEFAULT_SPECIALISTS = {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
84
|
+
// Language-keyed defaults (preserved for back-compat with v1.4.x callers
|
|
85
|
+
// that hand in a detectProjectType() result).
|
|
86
|
+
node: SOFTWARE_BENCH,
|
|
87
|
+
python: SOFTWARE_BENCH,
|
|
88
|
+
typed: TYPED_BENCH,
|
|
89
|
+
go: GO_RUST_BENCH,
|
|
90
|
+
rust: GO_RUST_BENCH,
|
|
91
|
+
other: OTHER_BENCH,
|
|
92
|
+
// Archetype-keyed defaults (new in v1.5.0 audit-MED-teams-#6) -- selected
|
|
93
|
+
// when the caller hands in a project archetype from the team detector.
|
|
94
|
+
software: SOFTWARE_BENCH,
|
|
95
|
+
book: BOOK_BENCH,
|
|
96
|
+
content: CONTENT_BENCH,
|
|
97
|
+
marketing: CONTENT_BENCH,
|
|
98
|
+
research: RESEARCH_BENCH,
|
|
99
|
+
design: CONTENT_BENCH,
|
|
100
|
+
business: SOFTWARE_BENCH,
|
|
101
|
+
mixed: SOFTWARE_BENCH,
|
|
42
102
|
};
|
|
43
103
|
|
|
104
|
+
// F-FUN-3: helper -- pick the right bench for a (language, archetype) pair.
|
|
105
|
+
// Archetype wins when supplied (a "book" project running from a tmp dir
|
|
106
|
+
// with no package.json must still receive the book bench). Falls back to
|
|
107
|
+
// language and then `other` so older callers keep working.
|
|
108
|
+
export function specialistsFor({ archetype, language } = {}) {
|
|
109
|
+
if (archetype && DEFAULT_SPECIALISTS[archetype]) return DEFAULT_SPECIALISTS[archetype];
|
|
110
|
+
if (language && DEFAULT_SPECIALISTS[language]) return DEFAULT_SPECIALISTS[language];
|
|
111
|
+
return DEFAULT_SPECIALISTS.other;
|
|
112
|
+
}
|
|
113
|
+
|
|
44
114
|
// Detects project type from filesystem signals in projectDir.
|
|
45
115
|
// Returns 'node' | 'python' | 'go' | 'rust' | 'typed' | 'other'.
|
|
46
116
|
export function detectProjectType(projectDir) {
|
|
@@ -74,14 +144,21 @@ function applySwarmDefaults(config) {
|
|
|
74
144
|
}
|
|
75
145
|
|
|
76
146
|
// Returns a fresh default config object for the given project type.
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
147
|
+
// F-FUN-3: supports an optional `archetype` keyed selection so non-software
|
|
148
|
+
// projects (book, content, research, ...) get a domain-tailored bench.
|
|
149
|
+
function buildDefault(projectType, options = {}) {
|
|
150
|
+
const specialists = specialistsFor({ archetype: options.archetype, language: projectType });
|
|
151
|
+
const config = { project_type: projectType, specialists: specialists.map(s => ({ ...s })) };
|
|
152
|
+
if (options.archetype) config.archetype = options.archetype;
|
|
153
|
+
return applySwarmDefaults(config);
|
|
80
154
|
}
|
|
81
155
|
|
|
82
156
|
// Reads .ijfw/swarm.json if present, otherwise detects type, generates
|
|
83
157
|
// default config, persists it, and returns it.
|
|
84
|
-
|
|
158
|
+
//
|
|
159
|
+
// F-FUN-3: optional `options.archetype` overrides language-only selection so
|
|
160
|
+
// brief-detected domains (book, content, research, ...) hit a tailored bench.
|
|
161
|
+
export function loadSwarmConfig(projectDir, options = {}) {
|
|
85
162
|
const swarmPath = join(projectDir, '.ijfw', 'swarm.json');
|
|
86
163
|
|
|
87
164
|
if (existsSync(swarmPath)) {
|
|
@@ -91,7 +168,7 @@ export function loadSwarmConfig(projectDir) {
|
|
|
91
168
|
}
|
|
92
169
|
|
|
93
170
|
const projectType = detectProjectType(projectDir);
|
|
94
|
-
const config = buildDefault(projectType);
|
|
171
|
+
const config = buildDefault(projectType, { archetype: options.archetype });
|
|
95
172
|
|
|
96
173
|
const ijfwDir = join(projectDir, '.ijfw');
|
|
97
174
|
if (!existsSync(ijfwDir)) mkdirSync(ijfwDir, { recursive: true });
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": "domain-template/v1",
|
|
3
|
+
"domain": "book",
|
|
4
|
+
"display_name": "Book / Long-form Narrative",
|
|
5
|
+
"description": "Novels, memoirs, non-fiction books, anthologies, and other long-form narrative works.",
|
|
6
|
+
"agent_ids": [
|
|
7
|
+
"ijfw-narrative-continuity-checker",
|
|
8
|
+
"ijfw-line-editor",
|
|
9
|
+
"ijfw-lore-keeper"
|
|
10
|
+
],
|
|
11
|
+
"agent_id_source": "domain-specialist",
|
|
12
|
+
"workflow_phases": ["outline", "draft", "revise", "review"],
|
|
13
|
+
"brief_fields": [
|
|
14
|
+
{
|
|
15
|
+
"key": "premise",
|
|
16
|
+
"type": "string",
|
|
17
|
+
"description": "Core premise or log-line — one to two sentences summarising what the book is about.",
|
|
18
|
+
"required": true
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"key": "genre",
|
|
22
|
+
"type": "string",
|
|
23
|
+
"description": "Genre or sub-genre (e.g. literary fiction, memoir, thriller, cookbook).",
|
|
24
|
+
"required": false
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"key": "characters",
|
|
28
|
+
"type": "array",
|
|
29
|
+
"description": "Named principal characters with a one-sentence description each.",
|
|
30
|
+
"required": false
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"key": "themes",
|
|
34
|
+
"type": "array",
|
|
35
|
+
"description": "Central themes the work explores (e.g. identity, grief, redemption).",
|
|
36
|
+
"required": false
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"key": "target_length",
|
|
40
|
+
"type": "string",
|
|
41
|
+
"description": "Approximate word-count target or chapter count.",
|
|
42
|
+
"required": false
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"key": "pov",
|
|
46
|
+
"type": "string",
|
|
47
|
+
"description": "Narrative point-of-view (first-person, third-person limited, omniscient, etc.).",
|
|
48
|
+
"required": false
|
|
49
|
+
}
|
|
50
|
+
]
|
|
51
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": "domain-template/v1",
|
|
3
|
+
"domain": "business",
|
|
4
|
+
"display_name": "Business / Strategy",
|
|
5
|
+
"description": "Business plans, strategy memos, GTM plans, investor decks, OKRs, and operational roadmaps.",
|
|
6
|
+
"agent_ids": [],
|
|
7
|
+
"agent_id_source": "domain-specialist",
|
|
8
|
+
"workflow_phases": ["diagnose", "plan", "decide", "review"],
|
|
9
|
+
"brief_fields": [
|
|
10
|
+
{
|
|
11
|
+
"key": "business_objective",
|
|
12
|
+
"type": "string",
|
|
13
|
+
"description": "Primary business objective this work must serve (e.g. enter new market, cut costs, raise Series A).",
|
|
14
|
+
"required": true
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"key": "stakeholders",
|
|
18
|
+
"type": "array",
|
|
19
|
+
"description": "Key stakeholders or decision-makers the output is written for.",
|
|
20
|
+
"required": false
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"key": "time_horizon",
|
|
24
|
+
"type": "string",
|
|
25
|
+
"description": "Planning horizon (e.g. 90-day, annual, 3-year).",
|
|
26
|
+
"required": false
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"key": "constraints",
|
|
30
|
+
"type": "array",
|
|
31
|
+
"description": "Known hard constraints: budget, headcount, regulatory, timeline.",
|
|
32
|
+
"required": false
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"key": "success_metrics",
|
|
36
|
+
"type": "array",
|
|
37
|
+
"description": "Measurable success criteria (e.g. revenue target, churn rate, NPS).",
|
|
38
|
+
"required": false
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": "domain-template/v1",
|
|
3
|
+
"domain": "content",
|
|
4
|
+
"display_name": "Content / Campaign",
|
|
5
|
+
"description": "Marketing campaigns, launch copy, blog posts, newsletters, social media, and SEO content.",
|
|
6
|
+
"agent_ids": [
|
|
7
|
+
"ijfw-campaign-strategist",
|
|
8
|
+
"ijfw-copy-reviewer"
|
|
9
|
+
],
|
|
10
|
+
"agent_id_source": "domain-specialist",
|
|
11
|
+
"workflow_phases": ["plan", "execute", "review", "publish"],
|
|
12
|
+
"brief_fields": [
|
|
13
|
+
{
|
|
14
|
+
"key": "objective",
|
|
15
|
+
"type": "string",
|
|
16
|
+
"description": "Primary campaign objective (e.g. product launch, lead gen, brand awareness).",
|
|
17
|
+
"required": true
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"key": "audience",
|
|
21
|
+
"type": "string",
|
|
22
|
+
"description": "Target audience segment — who the content is written for.",
|
|
23
|
+
"required": true
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"key": "channels",
|
|
27
|
+
"type": "array",
|
|
28
|
+
"description": "Distribution channels in scope (e.g. blog, email, Twitter/X, LinkedIn, press).",
|
|
29
|
+
"required": false
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"key": "tone",
|
|
33
|
+
"type": "string",
|
|
34
|
+
"description": "Brand voice and tone guidelines (e.g. authoritative, conversational, playful).",
|
|
35
|
+
"required": false
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"key": "key_messages",
|
|
39
|
+
"type": "array",
|
|
40
|
+
"description": "Top-line messages that every piece must reinforce.",
|
|
41
|
+
"required": false
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"key": "cta",
|
|
45
|
+
"type": "string",
|
|
46
|
+
"description": "Primary call-to-action the campaign drives toward.",
|
|
47
|
+
"required": false
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": "domain-template/v1",
|
|
3
|
+
"domain": "design",
|
|
4
|
+
"display_name": "Design / UX",
|
|
5
|
+
"description": "UI/UX design, design systems, brand identity, wireframes, and visual prototypes.",
|
|
6
|
+
"agent_ids": [
|
|
7
|
+
"ijfw-design-critic",
|
|
8
|
+
"ijfw-accessibility-reviewer"
|
|
9
|
+
],
|
|
10
|
+
"agent_id_source": "domain-specialist",
|
|
11
|
+
"workflow_phases": ["discovery", "shape", "execute", "review"],
|
|
12
|
+
"brief_fields": [
|
|
13
|
+
{
|
|
14
|
+
"key": "product",
|
|
15
|
+
"type": "string",
|
|
16
|
+
"description": "The product or surface being designed (e.g. mobile app, dashboard, landing page).",
|
|
17
|
+
"required": true
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"key": "design_goal",
|
|
21
|
+
"type": "string",
|
|
22
|
+
"description": "Primary design goal (e.g. reduce cognitive load, increase conversion, establish brand).",
|
|
23
|
+
"required": false
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"key": "platform",
|
|
27
|
+
"type": "string",
|
|
28
|
+
"description": "Target platform(s): web, iOS, Android, desktop, cross-platform.",
|
|
29
|
+
"required": false
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"key": "design_system",
|
|
33
|
+
"type": "string",
|
|
34
|
+
"description": "Existing design system or style guide in use (e.g. Material, Tailwind, proprietary).",
|
|
35
|
+
"required": false
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"key": "accessibility_target",
|
|
39
|
+
"type": "string",
|
|
40
|
+
"description": "Accessibility conformance target (e.g. WCAG 2.1 AA, Section 508).",
|
|
41
|
+
"required": false
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": "domain-template/v1",
|
|
3
|
+
"domain": "research",
|
|
4
|
+
"display_name": "Research / Analysis",
|
|
5
|
+
"description": "Academic papers, whitepapers, literature reviews, studies, and investigative analysis.",
|
|
6
|
+
"agent_ids": [],
|
|
7
|
+
"agent_id_source": "domain-specialist",
|
|
8
|
+
"workflow_phases": ["question", "collect", "synthesize", "review"],
|
|
9
|
+
"brief_fields": [
|
|
10
|
+
{
|
|
11
|
+
"key": "research_question",
|
|
12
|
+
"type": "string",
|
|
13
|
+
"description": "Primary research question or hypothesis the work addresses.",
|
|
14
|
+
"required": true
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"key": "methodology",
|
|
18
|
+
"type": "string",
|
|
19
|
+
"description": "Research methodology (e.g. qualitative, quantitative, mixed-methods, literature review).",
|
|
20
|
+
"required": false
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"key": "sources",
|
|
24
|
+
"type": "array",
|
|
25
|
+
"description": "Key source types or corpora to consult (e.g. academic databases, proprietary data, interviews).",
|
|
26
|
+
"required": false
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"key": "output_format",
|
|
30
|
+
"type": "string",
|
|
31
|
+
"description": "Intended output artefact (e.g. journal paper, internal report, executive summary).",
|
|
32
|
+
"required": false
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"key": "scope_constraints",
|
|
36
|
+
"type": "string",
|
|
37
|
+
"description": "Explicit scope limits: time range, geography, population, domain.",
|
|
38
|
+
"required": false
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": "domain-template/v1",
|
|
3
|
+
"domain": "software",
|
|
4
|
+
"display_name": "Software / Engineering",
|
|
5
|
+
"description": "Code, APIs, services, libraries, CLIs, mobile and web applications.",
|
|
6
|
+
"agent_ids": [
|
|
7
|
+
"ijfw-doc-verifier",
|
|
8
|
+
"ijfw-integration-checker",
|
|
9
|
+
"ijfw-nyquist-auditor",
|
|
10
|
+
"ijfw-code-fixer"
|
|
11
|
+
],
|
|
12
|
+
"agent_id_source": "software-core",
|
|
13
|
+
"workflow_phases": ["plan", "execute", "review", "verify"],
|
|
14
|
+
"brief_fields": [
|
|
15
|
+
{
|
|
16
|
+
"key": "stack",
|
|
17
|
+
"type": "string",
|
|
18
|
+
"description": "Primary language / framework stack (e.g. Node.js + Express, Python + FastAPI).",
|
|
19
|
+
"required": false
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"key": "architecture",
|
|
23
|
+
"type": "string",
|
|
24
|
+
"description": "High-level architecture pattern (e.g. monolith, microservices, serverless).",
|
|
25
|
+
"required": false
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"key": "entry_points",
|
|
29
|
+
"type": "array",
|
|
30
|
+
"description": "Key modules or endpoints the team should focus on first.",
|
|
31
|
+
"required": false
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"key": "test_strategy",
|
|
35
|
+
"type": "string",
|
|
36
|
+
"description": "Preferred test approach: unit, integration, e2e, TDD.",
|
|
37
|
+
"required": false
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
}
|