@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/team/generator.js
CHANGED
|
@@ -10,15 +10,262 @@ import { fileURLToPath } from 'node:url';
|
|
|
10
10
|
import { writeAtomic } from '../lib/atomic-io.js';
|
|
11
11
|
import { syncCodexAgents } from '../codex-agents.js';
|
|
12
12
|
import { detect } from '../project-type-detector.js';
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
DOMAIN_SPECIALIST_AGENT_IDS as CANONICAL_DOMAIN_SPECIALIST_AGENT_IDS,
|
|
15
|
+
SOFTWARE_CORE_AGENT_IDS as CANONICAL_SOFTWARE_CORE_AGENT_IDS,
|
|
16
|
+
assertValidTeamBundle,
|
|
17
|
+
validateTeamCharter,
|
|
18
|
+
validateWorkflowManifest,
|
|
19
|
+
} from './schemas.js';
|
|
14
20
|
|
|
15
21
|
const FIXTURE_DIR = resolve(fileURLToPath(new URL('../../fixtures/team/', import.meta.url)));
|
|
16
22
|
const SUPPORTED_ARCHETYPES = new Set(['software', 'design', 'content', 'book', 'research', 'business', 'mixed']);
|
|
17
23
|
|
|
24
|
+
// T24 / G7-core: the four universal software-core agents. Any software-
|
|
25
|
+
// domain roster MUST include all four. The ids resolve to static markdown
|
|
26
|
+
// files under `claude/agents/<id>.md`; the generator does not synthesise
|
|
27
|
+
// these — it references them by id and trusts the installer to deploy the
|
|
28
|
+
// markdown files into platform-native agent directories.
|
|
29
|
+
//
|
|
30
|
+
// Single source of truth lives in `./schemas.js` (so downstream validators
|
|
31
|
+
// can reference the canonical list without importing the generator). This
|
|
32
|
+
// re-export keeps the historic generator.js surface intact for callers
|
|
33
|
+
// already wired to `SOFTWARE_CORE_AGENT_IDS`.
|
|
34
|
+
//
|
|
35
|
+
// Static set (order is deterministic for snapshot stability):
|
|
36
|
+
// - ijfw-doc-verifier — factual-claim verification post-doc-gen
|
|
37
|
+
// - ijfw-integration-checker — cross-subagent E2E flow verification
|
|
38
|
+
// - ijfw-nyquist-auditor — coverage-gap closure + skeleton-test proposals
|
|
39
|
+
// - ijfw-code-fixer — atomic per-finding code fixes (G4 fixer)
|
|
40
|
+
export const SOFTWARE_CORE_AGENT_IDS = CANONICAL_SOFTWARE_CORE_AGENT_IDS;
|
|
41
|
+
|
|
42
|
+
// T25 / G7-gen: canonical per-domain specialist agent ids. Re-exported from
|
|
43
|
+
// schemas.js so callers wired to `generator.js` get a stable surface. See
|
|
44
|
+
// schemas.js for the per-archetype contract and the rationale for which
|
|
45
|
+
// archetypes are populated today.
|
|
46
|
+
export const DOMAIN_SPECIALIST_AGENT_IDS = CANONICAL_DOMAIN_SPECIALIST_AGENT_IDS;
|
|
47
|
+
|
|
48
|
+
// T24: archetypes that always include the software-core agent set.
|
|
49
|
+
// Currently only `software`; future domains (`mixed` with software files)
|
|
50
|
+
// may opt in via T25's domain-aware generator.
|
|
51
|
+
const SOFTWARE_CORE_ARCHETYPES = new Set(['software']);
|
|
52
|
+
|
|
53
|
+
// F-FUN-1: alias map -- detector returns language-flavoured labels and
|
|
54
|
+
// project-type-detector emits 'unknown' / unmapped domains. Canonicalize
|
|
55
|
+
// BEFORE the SUPPORTED_ARCHETYPES gate so detector outputs don't collapse
|
|
56
|
+
// to 'mixed' just because the literal string isn't in the supported set.
|
|
57
|
+
const ARCHETYPE_ALIASES = new Map([
|
|
58
|
+
// language-specific aliases that some detector paths surface
|
|
59
|
+
['typescript', 'software'],
|
|
60
|
+
['javascript', 'software'],
|
|
61
|
+
['python', 'software'],
|
|
62
|
+
['rust', 'software'],
|
|
63
|
+
['go', 'software'],
|
|
64
|
+
['java', 'software'],
|
|
65
|
+
['ruby', 'software'],
|
|
66
|
+
['php', 'software'],
|
|
67
|
+
['cpp', 'software'],
|
|
68
|
+
['c++', 'software'],
|
|
69
|
+
['csharp', 'software'],
|
|
70
|
+
['code', 'software'],
|
|
71
|
+
['app', 'software'],
|
|
72
|
+
['api', 'software'],
|
|
73
|
+
// domain synonyms surfaced by briefs and detector secondaries
|
|
74
|
+
['marketing', 'content'],
|
|
75
|
+
['campaign', 'content'],
|
|
76
|
+
['launch', 'content'],
|
|
77
|
+
['blog', 'content'],
|
|
78
|
+
['copy', 'content'],
|
|
79
|
+
['ui', 'design'],
|
|
80
|
+
['ux', 'design'],
|
|
81
|
+
['novel', 'book'],
|
|
82
|
+
['story', 'book'],
|
|
83
|
+
['manuscript', 'book'],
|
|
84
|
+
['paper', 'research'],
|
|
85
|
+
['study', 'research'],
|
|
86
|
+
['thesis', 'research'],
|
|
87
|
+
['strategy', 'business'],
|
|
88
|
+
['ops', 'business'],
|
|
89
|
+
['operations', 'business'],
|
|
90
|
+
['education', 'mixed'],
|
|
91
|
+
['unknown', 'mixed'],
|
|
92
|
+
]);
|
|
93
|
+
|
|
94
|
+
// F-FUN-1: brief keyword maps. Each phrase scores +1 toward the listed
|
|
95
|
+
// archetype on whole-word hit. Word boundaries matter -- "research" matches
|
|
96
|
+
// in "research project" but not "researching" -- so we tokenize the brief
|
|
97
|
+
// before scoring. Mirrors the canonical domain list in team-templates.md.
|
|
98
|
+
const BRIEF_KEYWORDS = {
|
|
99
|
+
software: [
|
|
100
|
+
'software', 'app', 'application', 'api', 'code', 'codebase', 'service',
|
|
101
|
+
'webapp', 'backend', 'frontend', 'mobile', 'sdk', 'library', 'cli',
|
|
102
|
+
'feature', 'endpoint', 'module', 'refactor', 'bugfix', 'plugin',
|
|
103
|
+
'platform', 'integration', 'pipeline',
|
|
104
|
+
],
|
|
105
|
+
book: [
|
|
106
|
+
'book', 'novel', 'novella', 'story', 'chapter', 'chapters', 'manuscript',
|
|
107
|
+
'memoir', 'fiction', 'nonfiction', 'prose', 'narrative', 'screenplay',
|
|
108
|
+
'cookbook', 'anthology',
|
|
109
|
+
],
|
|
110
|
+
content: [
|
|
111
|
+
'campaign', 'launch', 'marketing', 'content', 'blog', 'article',
|
|
112
|
+
'newsletter', 'copy', 'copywriting', 'landing', 'social', 'seo',
|
|
113
|
+
'email', 'post', 'posts', 'announcement', 'press',
|
|
114
|
+
],
|
|
115
|
+
design: [
|
|
116
|
+
'design', 'ui', 'ux', 'wireframe', 'mockup', 'figma', 'prototype',
|
|
117
|
+
'visual', 'brand', 'logo', 'illustration', 'typography', 'palette',
|
|
118
|
+
'design-system',
|
|
119
|
+
],
|
|
120
|
+
research: [
|
|
121
|
+
'research', 'paper', 'study', 'thesis', 'methodology', 'experiment',
|
|
122
|
+
'literature', 'corpus', 'survey', 'analysis', 'hypothesis', 'findings',
|
|
123
|
+
'whitepaper',
|
|
124
|
+
],
|
|
125
|
+
business: [
|
|
126
|
+
'business', 'strategy', 'operations', 'ops', 'financial', 'finance',
|
|
127
|
+
'budget', 'forecast', 'plan', 'pitch', 'investor', 'gtm', 'b2b', 'b2c',
|
|
128
|
+
'revenue', 'roadmap', 'okrs',
|
|
129
|
+
],
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// F-FUN-1: brief signal threshold. A single keyword hit isn't enough to
|
|
133
|
+
// override filesystem signals (a software repo whose README says "we plan to
|
|
134
|
+
// research X" should stay software). Two distinct keyword hits OR a strong
|
|
135
|
+
// signal phrase ("write a book", "marketing campaign", "research paper")
|
|
136
|
+
// flips the result. Phrases score double because they're explicit.
|
|
137
|
+
const BRIEF_PHRASES = {
|
|
138
|
+
software: [/\b(build|ship|develop|implement)\s+(?:a|an|the)?\s*(app|application|api|service|feature|module|library)\b/i, /\bsource\s+code\b/i],
|
|
139
|
+
book: [/\bwrite\s+(?:a|an|the)?\s*(book|novel|memoir|story|chapter)\b/i, /\bbook\s+about\b/i],
|
|
140
|
+
content: [/\b(marketing|launch|content|seo|social\s+media)\s+(campaign|strategy|plan|push)\b/i, /\bblog\s+post\b/i],
|
|
141
|
+
design: [/\b(design|brand|ui|ux)\s+(system|kit|guide|language|review)\b/i, /\bwireframe(?:s|d)?\s+(?:the|for|of)\b/i],
|
|
142
|
+
research: [/\bresearch\s+(paper|project|study|report|brief)\b/i, /\bliterature\s+review\b/i],
|
|
143
|
+
business: [/\b(business|strategy|operations)\s+(plan|roadmap|memo)\b/i, /\binvestor\s+(deck|pitch|memo)\b/i],
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const BRIEF_SCORE_FLIP_THRESHOLD = 2;
|
|
147
|
+
|
|
18
148
|
export function detectTeamArchetype(projectRoot = process.cwd(), options = {}) {
|
|
19
149
|
if (options.archetype) return normalizeArchetype(options.archetype);
|
|
150
|
+
|
|
151
|
+
// F-FUN-1: brief-first archetype routing. When the caller hands us a
|
|
152
|
+
// non-empty project brief, score it for explicit domain signals before
|
|
153
|
+
// falling back to filesystem-only detection. An explicit brief domain
|
|
154
|
+
// (e.g. "I'm writing a book about ...") MUST outweigh the filesystem
|
|
155
|
+
// signal -- otherwise book/research/campaign projects collapse to 'mixed'
|
|
156
|
+
// when run from a tmp directory or a freshly cloned repo with no files.
|
|
157
|
+
const brief = typeof options.brief === 'string' ? options.brief : '';
|
|
158
|
+
const briefScores = scoreBrief(brief);
|
|
159
|
+
const briefWinner = topBriefDomain(briefScores);
|
|
160
|
+
|
|
20
161
|
const detected = detect(projectRoot, { maxFiles: options.maxFiles || 4000, c9Available: false });
|
|
21
|
-
|
|
162
|
+
const detectedArchetype = normalizeArchetype(detected.primary_type || detected.type);
|
|
163
|
+
|
|
164
|
+
if (briefWinner) return briefWinner;
|
|
165
|
+
return detectedArchetype;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// F-FUN-1: brief scorer. Returns a map of {archetype: score}. Word-boundary
|
|
169
|
+
// match for single tokens, regex match for phrase signals (which count
|
|
170
|
+
// double). The caller picks the winner.
|
|
171
|
+
export function scoreBrief(brief) {
|
|
172
|
+
const scores = { software: 0, book: 0, content: 0, design: 0, research: 0, business: 0 };
|
|
173
|
+
if (!brief || typeof brief !== 'string') return scores;
|
|
174
|
+
const text = brief.toLowerCase();
|
|
175
|
+
|
|
176
|
+
for (const [domain, tokens] of Object.entries(BRIEF_KEYWORDS)) {
|
|
177
|
+
for (const token of tokens) {
|
|
178
|
+
const re = new RegExp(`\\b${escapeRegExp(token)}\\b`, 'i');
|
|
179
|
+
if (re.test(text)) scores[domain] += 1;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
for (const [domain, patterns] of Object.entries(BRIEF_PHRASES)) {
|
|
184
|
+
for (const re of patterns) {
|
|
185
|
+
if (re.test(text)) scores[domain] += 2;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return scores;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function topBriefDomain(scores) {
|
|
193
|
+
let best = null;
|
|
194
|
+
let bestScore = 0;
|
|
195
|
+
for (const [domain, score] of Object.entries(scores)) {
|
|
196
|
+
if (score > bestScore) {
|
|
197
|
+
best = domain;
|
|
198
|
+
bestScore = score;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
// Require at least the flip threshold AND a clear margin over runner-up.
|
|
202
|
+
// Without margin, "research the market for an app" ties software vs
|
|
203
|
+
// research and we should fall through to filesystem detection instead.
|
|
204
|
+
if (bestScore < BRIEF_SCORE_FLIP_THRESHOLD) return null;
|
|
205
|
+
const second = Object.entries(scores)
|
|
206
|
+
.filter(([d]) => d !== best)
|
|
207
|
+
.reduce((m, [, s]) => Math.max(m, s), 0);
|
|
208
|
+
if (second >= bestScore) return null;
|
|
209
|
+
return best;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function escapeRegExp(s) {
|
|
213
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// T24 / G7-core: resolve the static software-core agent set for an
|
|
217
|
+
// archetype. Returns `[]` for non-software archetypes. The returned ids
|
|
218
|
+
// each resolve to `claude/agents/<id>.md` in the IJFW repo — callers can
|
|
219
|
+
// look the files up via the installer's deploy step (the markdown is the
|
|
220
|
+
// agent spec; the generator doesn't render its content, it references
|
|
221
|
+
// it). Deterministic order.
|
|
222
|
+
export function resolveSoftwareCoreAgentIds(archetype) {
|
|
223
|
+
const normalized = normalizeArchetype(archetype);
|
|
224
|
+
if (!SOFTWARE_CORE_ARCHETYPES.has(normalized)) return [];
|
|
225
|
+
// Return a fresh array so callers cannot mutate the frozen source set
|
|
226
|
+
// through the public surface.
|
|
227
|
+
return [...SOFTWARE_CORE_AGENT_IDS];
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// T25 / G7-gen: resolve the per-domain specialist agent set. Returns the
|
|
231
|
+
// archetype's specialist ids per `DOMAIN_SPECIALIST_AGENT_IDS`, or `[]` if
|
|
232
|
+
// the archetype has no domain specialists yet (e.g. `research`, `business`,
|
|
233
|
+
// `mixed`). For `software` this returns `[]` — the software roster is
|
|
234
|
+
// covered by `resolveSoftwareCoreAgentIds`, not duplicated here.
|
|
235
|
+
//
|
|
236
|
+
// Deterministic order. Returns a fresh array per call.
|
|
237
|
+
export function resolveDomainSpecialistAgentIds(archetype) {
|
|
238
|
+
const normalized = normalizeArchetype(archetype);
|
|
239
|
+
const specialists = DOMAIN_SPECIALIST_AGENT_IDS[normalized];
|
|
240
|
+
if (!Array.isArray(specialists)) return [];
|
|
241
|
+
return [...specialists];
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// T25 / G7-gen: resolve the FULL roster for an archetype — the union of
|
|
245
|
+
// software-core agents (when applicable) plus domain specialists. This is
|
|
246
|
+
// the single function downstream callers (installers, dashboard tiles,
|
|
247
|
+
// `roster.synthesize` consumers) should reach for when they want the
|
|
248
|
+
// complete set of agents the generator believes a domain needs.
|
|
249
|
+
//
|
|
250
|
+
// Contract:
|
|
251
|
+
// - software archetype → all 4 SOFTWARE_CORE_AGENT_IDS, no specialists
|
|
252
|
+
// - book/content/design → only the domain specialists for that archetype
|
|
253
|
+
// - other archetypes → []
|
|
254
|
+
// - order is deterministic: software-core first, then domain specialists
|
|
255
|
+
// - no duplicates: even if a domain ever overlaps with a core id (it
|
|
256
|
+
// should not, by convention — see schemas.js), the union dedupes.
|
|
257
|
+
//
|
|
258
|
+
// The returned array is fresh per call so callers cannot mutate the
|
|
259
|
+
// canonical sources via the public surface.
|
|
260
|
+
export function resolveRosterForDomain(archetype) {
|
|
261
|
+
const normalized = normalizeArchetype(archetype);
|
|
262
|
+
const core = resolveSoftwareCoreAgentIds(normalized);
|
|
263
|
+
const specialists = resolveDomainSpecialistAgentIds(normalized);
|
|
264
|
+
const merged = [...core];
|
|
265
|
+
for (const id of specialists) {
|
|
266
|
+
if (!merged.includes(id)) merged.push(id);
|
|
267
|
+
}
|
|
268
|
+
return merged;
|
|
22
269
|
}
|
|
23
270
|
|
|
24
271
|
export function loadTeamTemplate(archetype) {
|
|
@@ -31,6 +278,9 @@ export function loadTeamTemplate(archetype) {
|
|
|
31
278
|
|
|
32
279
|
export function createTeamAssembly(projectRoot = process.cwd(), options = {}) {
|
|
33
280
|
const root = resolve(projectRoot);
|
|
281
|
+
// F-FUN-1: `options.brief` flows through to detectTeamArchetype so a
|
|
282
|
+
// CLI / MCP caller can hand in a project brief and get a correct
|
|
283
|
+
// archetype before any filesystem signals exist.
|
|
34
284
|
const archetype = detectTeamArchetype(root, options);
|
|
35
285
|
const bundle = loadTeamTemplate(archetype);
|
|
36
286
|
const teamName = options.teamName || `${basename(root) || archetype}-team`;
|
|
@@ -63,6 +313,21 @@ export function createTeamAssembly(projectRoot = process.cwd(), options = {}) {
|
|
|
63
313
|
}
|
|
64
314
|
const codexAgents = syncCodexAgents(root, { bundle });
|
|
65
315
|
|
|
316
|
+
// T24 / G7-core: software-domain rosters always advertise the static
|
|
317
|
+
// software-core agent set. Non-software archetypes get an empty array
|
|
318
|
+
// (preserves the field on every return shape so callers don't need to
|
|
319
|
+
// null-check). The ids point to `claude/agents/<id>.md` in the IJFW
|
|
320
|
+
// install; the installer is responsible for placing the markdown.
|
|
321
|
+
const softwareCoreAgentIds = resolveSoftwareCoreAgentIds(archetype);
|
|
322
|
+
|
|
323
|
+
// T25 / G7-gen: domain-specific specialist agent ids. Same on-disk
|
|
324
|
+
// contract as the software-core ids — each id resolves to
|
|
325
|
+
// `claude/agents/<id>.md`. T25 returns the ids; T26 lands the matching
|
|
326
|
+
// markdown files. Until T26 ships, downstream installers should treat
|
|
327
|
+
// a missing file as "deploy stub" rather than fail-closed.
|
|
328
|
+
const domainSpecialistAgentIds = resolveDomainSpecialistAgentIds(archetype);
|
|
329
|
+
const rosterAgentIds = resolveRosterForDomain(archetype);
|
|
330
|
+
|
|
66
331
|
return {
|
|
67
332
|
ok: true,
|
|
68
333
|
archetype,
|
|
@@ -73,6 +338,9 @@ export function createTeamAssembly(projectRoot = process.cwd(), options = {}) {
|
|
|
73
338
|
workflowPath,
|
|
74
339
|
agentFiles,
|
|
75
340
|
codexAgents,
|
|
341
|
+
softwareCoreAgentIds,
|
|
342
|
+
domainSpecialistAgentIds,
|
|
343
|
+
rosterAgentIds,
|
|
76
344
|
};
|
|
77
345
|
}
|
|
78
346
|
|
|
@@ -105,7 +373,14 @@ export function readTeamAssembly(projectRoot = process.cwd()) {
|
|
|
105
373
|
|
|
106
374
|
function normalizeArchetype(value) {
|
|
107
375
|
const archetype = String(value || '').toLowerCase();
|
|
108
|
-
|
|
376
|
+
if (SUPPORTED_ARCHETYPES.has(archetype)) return archetype;
|
|
377
|
+
// F-FUN-1: canonicalize via the alias map BEFORE collapsing to 'mixed'.
|
|
378
|
+
// Without this, the detector's language-flavoured outputs (typescript,
|
|
379
|
+
// javascript, python, ...) and project-type-detector's 'unknown' all
|
|
380
|
+
// collapse to 'mixed', losing the strongest signal we have.
|
|
381
|
+
const aliased = ARCHETYPE_ALIASES.get(archetype);
|
|
382
|
+
if (aliased && SUPPORTED_ARCHETYPES.has(aliased)) return aliased;
|
|
383
|
+
return 'mixed';
|
|
109
384
|
}
|
|
110
385
|
|
|
111
386
|
function renderAgent(role, bundle) {
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
// F-FUN-4 + F-FUN-5 (audit-MED-teams-#7 + #13): team-charter mutation
|
|
2
|
+
// helpers. Honors the SKILL.md "Custom Agent Requests" promise -- users can
|
|
3
|
+
// add, remove, swap, list, and check roles on their custom team without
|
|
4
|
+
// hand-editing JSON.
|
|
5
|
+
//
|
|
6
|
+
// Every mutating helper:
|
|
7
|
+
// 1. Reads the current charter/workflow.
|
|
8
|
+
// 2. Applies the mutation in-memory.
|
|
9
|
+
// 3. Validates the resulting charter via team/schemas.js.
|
|
10
|
+
// 4. Writes the charter back atomically.
|
|
11
|
+
// 5. Re-runs syncCodexAgents so .codex/agents/ stays in lockstep.
|
|
12
|
+
//
|
|
13
|
+
// `checkTeamAssembly` is the standalone validator surfaced by
|
|
14
|
+
// `ijfw team check` -- it does not write anything and does not require
|
|
15
|
+
// `ijfw swarm plan` to surface error messages.
|
|
16
|
+
|
|
17
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
18
|
+
import { join, resolve } from 'node:path';
|
|
19
|
+
import { writeAtomic } from '../lib/atomic-io.js';
|
|
20
|
+
import { syncCodexAgents } from '../codex-agents.js';
|
|
21
|
+
import { readTeamAssembly } from './generator.js';
|
|
22
|
+
import { validateTeamCharter, validateWorkflowManifest } from './schemas.js';
|
|
23
|
+
|
|
24
|
+
function teamPaths(root) {
|
|
25
|
+
return {
|
|
26
|
+
charterPath: join(root, '.ijfw', 'team', 'charter.json'),
|
|
27
|
+
workflowPath: join(root, '.ijfw', 'team', 'workflow.json'),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function loadCharter(root) {
|
|
32
|
+
const { charterPath } = teamPaths(root);
|
|
33
|
+
if (!existsSync(charterPath)) return null;
|
|
34
|
+
return JSON.parse(readFileSync(charterPath, 'utf8'));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function loadWorkflow(root) {
|
|
38
|
+
const { workflowPath } = teamPaths(root);
|
|
39
|
+
if (!existsSync(workflowPath)) return null;
|
|
40
|
+
return JSON.parse(readFileSync(workflowPath, 'utf8'));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function writeCharter(root, charter) {
|
|
44
|
+
const { charterPath } = teamPaths(root);
|
|
45
|
+
writeAtomic(charterPath, `${JSON.stringify(charter, null, 2)}\n`, { mode: 0o600 });
|
|
46
|
+
return charterPath;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* List the current roles + summary metadata for the team. Returns a
|
|
51
|
+
* structured payload so callers (CLI + future MCP tool) can render however
|
|
52
|
+
* they like.
|
|
53
|
+
*/
|
|
54
|
+
export function listTeamRoles(projectRoot = process.cwd()) {
|
|
55
|
+
const root = resolve(projectRoot);
|
|
56
|
+
const charter = loadCharter(root);
|
|
57
|
+
if (!charter) return { ok: false, error: 'missing-charter' };
|
|
58
|
+
const roles = Array.isArray(charter.roles) ? charter.roles : [];
|
|
59
|
+
return {
|
|
60
|
+
ok: true,
|
|
61
|
+
team_name: charter.team_name || null,
|
|
62
|
+
project_archetypes: charter.project_archetypes || [],
|
|
63
|
+
roles: roles.map((r) => ({
|
|
64
|
+
name: r.name,
|
|
65
|
+
role_type: r.role_type,
|
|
66
|
+
model: r.model,
|
|
67
|
+
effort: r.effort || 'medium',
|
|
68
|
+
phase_scope: r.phase_scope || [],
|
|
69
|
+
})),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Add a role to the charter. The charter is supplied as a JSON path; we
|
|
75
|
+
* read+validate it before splicing in. Triggers a codex agent re-sync.
|
|
76
|
+
*/
|
|
77
|
+
export function addTeamRole(projectRoot, { charterPath, role }) {
|
|
78
|
+
const root = resolve(projectRoot);
|
|
79
|
+
const charter = loadCharter(root);
|
|
80
|
+
if (!charter) return { ok: false, error: 'missing-charter' };
|
|
81
|
+
|
|
82
|
+
const incoming = role || readJsonFile(charterPath);
|
|
83
|
+
if (!incoming || typeof incoming !== 'object') return { ok: false, error: 'invalid-role-payload' };
|
|
84
|
+
if (!incoming.name) return { ok: false, error: 'role-name-required' };
|
|
85
|
+
|
|
86
|
+
const existing = (charter.roles || []).find((r) => r.name === incoming.name);
|
|
87
|
+
if (existing) return { ok: false, error: 'role-exists', name: incoming.name };
|
|
88
|
+
|
|
89
|
+
const next = { ...charter, roles: [...(charter.roles || []), incoming] };
|
|
90
|
+
const validation = validateTeamCharter(next);
|
|
91
|
+
if (!validation.ok) return { ok: false, error: 'invalid-charter', errors: validation.errors };
|
|
92
|
+
|
|
93
|
+
writeCharter(root, next);
|
|
94
|
+
const codex = syncCodexAgents(root);
|
|
95
|
+
return { ok: true, role: incoming, codex };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Remove a role by name. Refuses if removing the role would leave the
|
|
100
|
+
* charter invalid (e.g. last role).
|
|
101
|
+
*/
|
|
102
|
+
export function removeTeamRole(projectRoot, { name }) {
|
|
103
|
+
const root = resolve(projectRoot);
|
|
104
|
+
const charter = loadCharter(root);
|
|
105
|
+
if (!charter) return { ok: false, error: 'missing-charter' };
|
|
106
|
+
|
|
107
|
+
const before = (charter.roles || []).length;
|
|
108
|
+
const roles = (charter.roles || []).filter((r) => r.name !== name);
|
|
109
|
+
if (roles.length === before) return { ok: false, error: 'role-not-found', name };
|
|
110
|
+
|
|
111
|
+
const next = { ...charter, roles };
|
|
112
|
+
const validation = validateTeamCharter(next);
|
|
113
|
+
if (!validation.ok) return { ok: false, error: 'invalid-charter', errors: validation.errors };
|
|
114
|
+
|
|
115
|
+
writeCharter(root, next);
|
|
116
|
+
const codex = syncCodexAgents(root);
|
|
117
|
+
return { ok: true, removed: name, codex };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Replace one role with another. `oldName` must exist; `replacement` is a
|
|
122
|
+
* full role object (or path to a JSON file describing one).
|
|
123
|
+
*/
|
|
124
|
+
export function swapTeamRole(projectRoot, { oldName, charterPath, replacement }) {
|
|
125
|
+
const root = resolve(projectRoot);
|
|
126
|
+
const charter = loadCharter(root);
|
|
127
|
+
if (!charter) return { ok: false, error: 'missing-charter' };
|
|
128
|
+
|
|
129
|
+
const incoming = replacement || readJsonFile(charterPath);
|
|
130
|
+
if (!incoming || typeof incoming !== 'object') return { ok: false, error: 'invalid-role-payload' };
|
|
131
|
+
if (!incoming.name) return { ok: false, error: 'role-name-required' };
|
|
132
|
+
|
|
133
|
+
const roles = charter.roles || [];
|
|
134
|
+
const idx = roles.findIndex((r) => r.name === oldName);
|
|
135
|
+
if (idx === -1) return { ok: false, error: 'role-not-found', name: oldName };
|
|
136
|
+
|
|
137
|
+
// Name collision when the replacement keeps a *different* existing name.
|
|
138
|
+
if (incoming.name !== oldName && roles.some((r) => r.name === incoming.name)) {
|
|
139
|
+
return { ok: false, error: 'role-exists', name: incoming.name };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const nextRoles = roles.slice();
|
|
143
|
+
nextRoles[idx] = incoming;
|
|
144
|
+
const next = { ...charter, roles: nextRoles };
|
|
145
|
+
const validation = validateTeamCharter(next);
|
|
146
|
+
if (!validation.ok) return { ok: false, error: 'invalid-charter', errors: validation.errors };
|
|
147
|
+
|
|
148
|
+
writeCharter(root, next);
|
|
149
|
+
const codex = syncCodexAgents(root);
|
|
150
|
+
return { ok: true, swapped: { old: oldName, new: incoming.name }, codex };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* F-FUN-5 (audit-MED-teams-#13): standalone team validation. Returns a
|
|
155
|
+
* structured pass/fail report so the CLI can render a human-readable list
|
|
156
|
+
* without forcing the caller into `ijfw swarm plan`.
|
|
157
|
+
*/
|
|
158
|
+
export function checkTeamAssembly(projectRoot = process.cwd()) {
|
|
159
|
+
const root = resolve(projectRoot);
|
|
160
|
+
const charter = loadCharter(root);
|
|
161
|
+
const workflow = loadWorkflow(root);
|
|
162
|
+
const report = {
|
|
163
|
+
ok: true,
|
|
164
|
+
has_charter: Boolean(charter),
|
|
165
|
+
has_workflow: Boolean(workflow),
|
|
166
|
+
charter: { ok: true, errors: [] },
|
|
167
|
+
workflow: { ok: true, errors: [] },
|
|
168
|
+
role_count: 0,
|
|
169
|
+
artifact_count: 0,
|
|
170
|
+
};
|
|
171
|
+
if (!charter) {
|
|
172
|
+
report.ok = false;
|
|
173
|
+
report.charter = { ok: false, errors: ['charter.json is missing -- run: ijfw team init'] };
|
|
174
|
+
} else {
|
|
175
|
+
const c = validateTeamCharter(charter);
|
|
176
|
+
report.charter = c;
|
|
177
|
+
report.role_count = Array.isArray(charter.roles) ? charter.roles.length : 0;
|
|
178
|
+
if (!c.ok) report.ok = false;
|
|
179
|
+
}
|
|
180
|
+
if (!workflow) {
|
|
181
|
+
report.ok = false;
|
|
182
|
+
report.workflow = { ok: false, errors: ['workflow.json is missing -- run: ijfw team init'] };
|
|
183
|
+
} else {
|
|
184
|
+
const w = validateWorkflowManifest(workflow, charter);
|
|
185
|
+
report.workflow = w;
|
|
186
|
+
report.artifact_count = Array.isArray(workflow.artifacts) ? workflow.artifacts.length : 0;
|
|
187
|
+
if (!w.ok) report.ok = false;
|
|
188
|
+
}
|
|
189
|
+
return report;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function readJsonFile(path) {
|
|
193
|
+
if (!path) return null;
|
|
194
|
+
if (!existsSync(path)) return null;
|
|
195
|
+
try {
|
|
196
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
197
|
+
} catch {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Re-exports for the CLI surface so it can `import * as teamModify`.
|
|
203
|
+
export { readTeamAssembly };
|
package/src/team/schemas.js
CHANGED
|
@@ -10,6 +10,54 @@ const ARCHETYPES = new Set([
|
|
|
10
10
|
'mixed',
|
|
11
11
|
]);
|
|
12
12
|
|
|
13
|
+
// T24 / G7-core: canonical software-core agent ids. Mirrored by
|
|
14
|
+
// `SOFTWARE_CORE_AGENT_IDS` in generator.js — this re-export gives
|
|
15
|
+
// schema-side consumers (validators, downstream tools, T25's domain-aware
|
|
16
|
+
// generator) a single source of truth without circular import on
|
|
17
|
+
// generator.js. Any change to the set MUST update both locations and the
|
|
18
|
+
// matching `claude/agents/<id>.md` files on disk.
|
|
19
|
+
export const SOFTWARE_CORE_AGENT_IDS = Object.freeze([
|
|
20
|
+
'ijfw-doc-verifier',
|
|
21
|
+
'ijfw-integration-checker',
|
|
22
|
+
'ijfw-nyquist-auditor',
|
|
23
|
+
'ijfw-code-fixer',
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
// T25 / G7-gen: canonical domain-specialist agent ids by archetype. The map
|
|
27
|
+
// is the single source of truth for which specialist ids the generator
|
|
28
|
+
// returns for each domain — T26 will author the matching
|
|
29
|
+
// `claude/agents/<id>.md` files so the installer can deploy them; T25 just
|
|
30
|
+
// makes the generator hand them back per archetype.
|
|
31
|
+
//
|
|
32
|
+
// Contract:
|
|
33
|
+
// - keys are normalized archetypes (post-alias-map). `book`, `content`,
|
|
34
|
+
// and `design` are populated today; other archetypes get `[]` until
|
|
35
|
+
// T26 expands the set.
|
|
36
|
+
// - values are deterministic arrays; insertion order is roster-display order.
|
|
37
|
+
// - software domain's specialists are the SOFTWARE_CORE_AGENT_IDS — this
|
|
38
|
+
// map intentionally does NOT duplicate them (`resolveDomainSpecialistAgentIds`
|
|
39
|
+
// in generator.js returns the union for software). Non-software domains
|
|
40
|
+
// own their own specialist lists exclusively.
|
|
41
|
+
//
|
|
42
|
+
// Choice of three+ domains (`book`, `content`, `design`) satisfies the
|
|
43
|
+
// "≥3 non-software domains" constraint and gives T26 concrete templates
|
|
44
|
+
// to flesh out. Other archetypes will gain rosters in later tasks.
|
|
45
|
+
export const DOMAIN_SPECIALIST_AGENT_IDS = Object.freeze({
|
|
46
|
+
book: Object.freeze([
|
|
47
|
+
'ijfw-narrative-continuity-checker',
|
|
48
|
+
'ijfw-line-editor',
|
|
49
|
+
'ijfw-lore-keeper',
|
|
50
|
+
]),
|
|
51
|
+
content: Object.freeze([
|
|
52
|
+
'ijfw-campaign-strategist',
|
|
53
|
+
'ijfw-copy-reviewer',
|
|
54
|
+
]),
|
|
55
|
+
design: Object.freeze([
|
|
56
|
+
'ijfw-design-critic',
|
|
57
|
+
'ijfw-accessibility-reviewer',
|
|
58
|
+
]),
|
|
59
|
+
});
|
|
60
|
+
|
|
13
61
|
const ROLE_TYPES = new Set([
|
|
14
62
|
'lead',
|
|
15
63
|
'software',
|
package/src/update-apply.js
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
// MCP tool: ijfw_update_apply
|
|
2
2
|
//
|
|
3
|
+
// @deprecated since v1.5.0; will be removed in v1.6.0 (F-FUN-3 / v1.5.0 audit-MED-M7).
|
|
4
|
+
// `ijfw_update_check` already issues a confirmation token whose instruction tells
|
|
5
|
+
// the user to type `ijfw update --confirm <token>` in their terminal directly.
|
|
6
|
+
// The intermediate `ijfw_update_apply` step writes a pending sentinel, but the
|
|
7
|
+
// terminal CLI does not require the sentinel to confirm — the token itself is
|
|
8
|
+
// authoritative. The tool is retained for v1.5.0 back-compat (older skills that
|
|
9
|
+
// still call it work unchanged) and slated for retirement in v1.6.0 to free the
|
|
10
|
+
// MCP-tool slot (see CLAUDE.md "MCP server: ≤12 tools" cap).
|
|
11
|
+
//
|
|
3
12
|
// Does NOT execute the update. Validates the token, writes (or overwrites)
|
|
4
13
|
// the pending sentinel, returns instruction telling the user to run the
|
|
5
14
|
// terminal-side confirm command. Idempotent against a matching sentinel
|
|
@@ -10,6 +19,11 @@
|
|
|
10
19
|
import { validateToken, writePendingSentinel } from './lib/token.js';
|
|
11
20
|
import { isVersionStringValid } from './lib/npm-view.js';
|
|
12
21
|
|
|
22
|
+
/**
|
|
23
|
+
* @deprecated since v1.5.0; scheduled for removal in v1.6.0. Callers should
|
|
24
|
+
* skip straight from `ijfw_update_check` to the terminal-side confirm command;
|
|
25
|
+
* the intermediate sentinel write is redundant given the token contract.
|
|
26
|
+
*/
|
|
13
27
|
export function ijfwUpdateApply(args = {}) {
|
|
14
28
|
const { target_version, confirmation_token } = args || {};
|
|
15
29
|
const sessionId = args.session_id || process.env.IJFW_SESSION_ID || 'default-session';
|
|
@@ -64,9 +78,11 @@ export function ijfwUpdateApply(args = {}) {
|
|
|
64
78
|
export const TOOL_DEF = {
|
|
65
79
|
name: 'ijfw_update_apply',
|
|
66
80
|
description:
|
|
67
|
-
'Stage an IJFW update behind out-of-band terminal
|
|
68
|
-
|
|
69
|
-
'This MCP tool NEVER executes the
|
|
81
|
+
'[DEPRECATED v1.5.0; removal in v1.6.0] Stage an IJFW update behind out-of-band terminal ' +
|
|
82
|
+
'confirmation. Writes a pending sentinel; actual update only runs when the user types ' +
|
|
83
|
+
"'ijfw update --confirm <token>' in their terminal. This MCP tool NEVER executes the " +
|
|
84
|
+
'update directly. Prefer calling ijfw_update_check and forwarding the returned ' +
|
|
85
|
+
"'ijfw update --confirm <token>' instruction directly to the user.",
|
|
70
86
|
inputSchema: {
|
|
71
87
|
type: 'object',
|
|
72
88
|
required: ['target_version', 'confirmation_token'],
|