The Enterprise Architecture Governance Harness - 76 slash commands across strategy, architecture, delivery, and assurance

The Enterprise Architecture Governance Harness — a Claude Code plugin providing 76 slash commands across strategy, architecture, delivery, assurance, and interoperability.
ArcKit needs Claude Code v2.1.287 or later (see Prerequisites below). The simplest way to land on a supported version — even if you've never installed Claude Code before — is:
claude install latest
If you don't yet have the claude CLI on your PATH, follow the official Claude Code install guide first, then run claude install latest. To check what you're on:
claude --version
In Claude Code, run:
/plugin marketplace add tractorjuice/arckit-claude
/plugin
Go to the Discover tab, find arckit, and install it. Or via CLI:
claude plugin install arckit@arckit-claude
The same marketplace hosts the core plugin plus regional, sector, and tooling overlays. Install only the plugins you need:
claude plugin install arckit arckit-uae
claude plugin install arckit arckit-au arckit-au-energy
claude plugin install arckit arckit-uk-finance
The arckit-uk-gcloud overlay is public for installation and inspection, but remains proprietary and is not MIT licensed.
claude --plugin-dir /path/to/arckit-claude
/arckit:aws-research: AWS Knowledge MCP server (included)/arckit:azure-research: Microsoft Learn MCP server (included)/arckit:gcp-research: Google Developer Knowledge MCP (requires GOOGLE_API_KEY — see MCP Servers)Why v2.1.287? v2.1.287 adds Claude mods, which ArcKit uses to draw a status line above the prompt: how many projects and artefacts you have, how many are DRAFT, and how many reviews are overdue. It also fixes plugin SessionStart hooks not running in new cloud sessions, which skipped ArcKit's session set-up and version check there. The floor carries v2.1.284, which adds Claude Sonnet 5.5 (
claude-sonnet-5-5), the default Sonnet model on the Anthropic API, with 1M context; earlier clients cannot select it. Like Opus 5.5 it always thinks and defaults toeffort: medium, so ArcKit'seffort: maxcommands run atmaxon it. The floor carries v2.1.280, which adds Claude Opus 5.5 (claude-opus-5-5), the default Opus model. Opus 5.5 always thinks, so ArcKit'seffort: maxcommands can no longer be quietly sent ashighby a session with thinking off, and the Effective Effort row in each artefact's Build Provenance is accurate. The floor carries v2.1.251, which stops the file tools following a symlink swapped inside the working directory after the permission check, and makes Grep and Glob honourRead()deny rules through symlinked paths — the class of bypass ArcKit'sfile-protectionandsecret-file-scannergates sit in front of. It also sends Opus 5effort: xhigh/maxashighwhen thinking is off instead of failing, so ArcKit'seffort: maxcommands complete on thinking-off sessions. v2.1.246 fixed four plugin-loading bugs that hit ArcKit's exact layout:/reload-pluginscounted 0 skills forskills/*/SKILL.mdplugins, hook errors showed a literal${CLAUDE_PLUGIN_ROOT}, the plugin cache created duplicate SHA-named directories, andclaude plugin update <bare-name>failed. The floor carries forward v2.1.234, which stops Claude Code's MCP diagnostics printing resolved secrets — ArcKit bundles two keyed MCP servers whose${user_config.*}API keys sit in request headers, and on a keyless session those connections fail by design, so ArcKit routinely produces exactly the diagnostics this fixed. v2.1.221 fixedWebSearchreturning a 400 ateffort: xhigh/maxwith thinking disabled, which silently broke ArcKit'seffort: maxcommands and research agents for anyone running with thinking off. v2.1.222-v2.1.224 close PreToolUse auto-allow bypasses in background agent tasks (load-bearing since v2.1.232 made spawns background by default), Bash permission-check bypasses, and a sandbox deny-path bypass. It also carries v2.1.219's Claude Opus 5 (claude-opus-5) with 1M context and fast mode (superseded as the default by Opus 5.5); v2.1.200's project-scoped plugin loading from git worktrees andclaude agents --plugin-dir <dir>visibility; the v2.1.198-v2.1.199 background-subagent reliability, parent error-propagation, and hook stderr-visibility fixes; v2.1.197's Claude Sonnet 5 default with native 1M context; and v2.1.172's wildcard-domainWebFetchfix. It also carries the older MCPalwaysLoad, provenance hook, release validation, telemetry,/context, Auto mode, plugin update, MCP leak, retry, and subagent working-directory fixes ArcKit relies on.
After installing the plugin:
/arckit:init
/arckit:principles
/arckit:requirements NHS appointment booking system
| Component | Count | Description |
|---|---|---|
| Commands | 75 | Slash commands for architecture artifacts and OKF interoperability |
| Skills | 1 | Conversational Wardley Mapping with interactive guidance |
| Agents | 20 | Autonomous research agents and subagent definitions |
| Templates | 68 | Document templates with UK Government compliance |
| Scripts | 15 | Helper bash, Python, and Node scripts |
| Hooks | 17 | Automation hooks across 7 event types |
| Guides | 167 | Command and reference documentation |
Automation hooks run automatically to provide context and enforce standards. See the Hooks Guide for full details. docs/ENFORCEMENT.md states which rules the hooks enforce in code, which are only asked of the model, and what your organisation supplies.
| Event | Hooks | Purpose |
|---|---|---|
| SessionStart | arckit-session, version-check | Inject version/context, check for updates |
| Stop / StopFailure | session-learner | Record session activity for future context |
| UserPromptSubmit | arckit-context, secret-detection, + 6 command-specific | Project context, secret scanning, pre-processing |
| PreToolUse | validate-arc-filename, score-validator, file-protection, secret-file-scanner | Filename enforcement, security, validation |
| PostToolUse | update-manifest | Keep manifest.json in sync |
/arckit:export-okf exports ArcKit ARC-*.md artifacts as copied Markdown with OKF-compatible frontmatter./arckit:import-okf scans OKF Markdown bundles, writes .arckit/tmp/okf-import-report.json, and materializes safe imports as RSCH review notes.ARCKIT_OKF_FRONTMATTER=1 or .arckit/config.json with { "okfFrontmatter": true }.ArcKit templates can be customized per-project to match your organization's requirements, branding, or compliance frameworks.
.arckit/templates/, it takes precedence over the plugin's default template./arckit:customize to copy templates to your project for editing.# List available templates
/arckit:customize list
# Copy a template to customize
/arckit:customize requirements
# Copy all templates
/arckit:customize all
For non-UK Government projects:
For your organization:
project-root/
├── .arckit/
│ └── templates/ # Your customized templates
│ ├── requirements-template.md
│ ├── risk-register-template.md
│ └── ...
└── projects/
└── ...
When ArcKit plugin updates with new features:
The plugin includes conversational skills that activate automatically when you ask relevant questions:
/arckit:wardley instead./arckit:principles - Create architecture principles/arckit:stakeholders - Analyze stakeholders and goals/arckit:requirements - Generate comprehensive requirements/arckit:risk - Create risk register (Orange Book)/arckit:sobc - Strategic Outline Business Case (Green Book)/arckit:data-model - Data model with GDPR compliance/arckit:diagram - Architecture diagrams (Mermaid)/arckit:wardley - Wardley Maps for strategy/arckit:adr - Architecture Decision Records/arckit:research - Technology market research/arckit:aws-research - AWS service research (MCP)/arckit:azure-research - Azure service research (MCP)/arckit:datascout - External data source discovery/arckit:evaluate - Vendor evaluation framework/arckit:sow - Statement of Work / RFP/arckit:gcloud-search - G-Cloud marketplace search/arckit:dos - Digital Outcomes & Specialists/arckit:tcop - Technology Code of Practice review/arckit:secure - Secure by Design assessment/arckit:dpia - Data Protection Impact Assessment/arckit:ai-playbook - AI Playbook compliance/arckit:service-assessment - GDS Service Standard/arckit:devops - DevOps strategy/arckit:finops - FinOps cloud cost management/arckit:mlops - MLOps strategy/arckit:operationalize - Operational readiness/arckit:backlog - Product backlog generation/arckit:roadmap - Architecture roadmapSee the full command list with /help arckit.
ArcKit creates this structure in your project:
projects/
├── 000-global/ # Cross-project artifacts
│ ├── policies/ # Organization policies
│ └── ARC-000-PRIN-*.md # Architecture principles
└── 001-project-name/ # Project artifacts
├── ARC-001-REQ-*.md # Requirements
├── ARC-001-STKE-*.md # Stakeholders
├── vendors/ # Vendor evaluations
└── external/ # External documents
The plugin includes 7 MCP (Model Context Protocol) servers: six for cloud and government research, and Trello for backlog export:
| MCP Server | API Key Required | Used By |
|---|---|---|
| AWS Knowledge | No | /arckit:aws-research |
| Microsoft Learn | No | /arckit:azure-research |
| Google Developer Knowledge | Yes (GOOGLE_API_KEY) | /arckit:gcp-research |
| Data Commons | Yes (DATA_COMMONS_API_KEY) | Data statistics lookups |
| govreposcrape | No | /arckit:gov-reuse, /arckit:gov-code-search, /arckit:gov-landscape |
| UK Tenders | No | /arckit:tenders, /arckit:competitors |
| Trello (Atlassian) | No; sign in once through /mcp | /arckit:trello |
AWS Knowledge and Microsoft Learn work out of the box with no configuration. The Google and Data Commons servers require API keys — if you don't set them, you'll see errors in the plugin UI, but all other commands work normally.
Google Developer Knowledge (for /arckit:gcp-research):
export GOOGLE_API_KEY="your-key-here"Data Commons (for data statistics lookups):
export DATA_COMMONS_API_KEY="your-key-here"ArcKit collects no usage data. It has no analytics and no account, and it runs no server of its own. Everything it writes stays in your repository: the artefacts under projects/, and the session log and telemetry the hooks keep under .arckit/memory/ on your machine.
The plugin sends data to other services only in these cases:
| When | Where it goes | What is sent |
|---|---|---|
| A command queries one of the six bundled research MCP servers (table above) | AWS Knowledge (knowledge-mcp.global.api.aws), Microsoft Learn (learn.microsoft.com), Google Developer Knowledge (developerknowledge.googleapis.com), Data Commons (api.datacommons.org), govreposcrape (govreposcrape-api-1060386346356.us-central1.run.app), UK Tenders (tenders.run.cns.me) | The search terms and document IDs Claude passes to that server's tools, which can include wording taken from your requirements. The Google and Data Commons servers also receive the API key you configure. Each service's own privacy policy applies |
/arckit:trello exports a backlog | Atlassian's Trello MCP server (mcp.trello.com) | The backlog's stories, descriptions and acceptance criteria, written to a board in your Trello account under your own sign-in. Atlassian's privacy policy applies |
| Each session starts | GitHub API (api.github.com) | One anonymous request for the latest ArcKit release, to tell you when an update is available. No project data is sent |
You run /arckit:trello | Trello API (api.trello.com) | Your backlog's epics, stories and acceptance criteria, sent with the Trello key and token you provide |
| A research command uses web search or fetch | The sites Claude searches or fetches | Search queries and URLs, through Claude's own web tools |
No hook approves a permission request. Each command and skill pre-approves what it needs through its own allowed-tools rules, which Claude Code applies only while that command runs: reading the plugin's own files (Read(/${CLAUDE_PLUGIN_ROOT}/**), for its templates, references and schemas, which sit outside your working directory) and running the plugin's own scripts. Your own deny rules still take precedence. MCP tool calls ask the first time; to stop the prompts for a server, add it to permissions.allow, for example "mcp__plugin_arckit_aws-knowledge". Before running ArcKit on sensitive material, see docs/ENFORCEMENT.md and the privacy policy at <https://arckit.org/privacy.html>.
If you previously used arckit init --ai claude:
# Remove CLI-generated files (plugin replaces them)
rm -rf .claude/commands/arckit.*.md
rm -rf .claude/agents/arckit-*.md
rm -rf .arckit/templates/
rm -rf .arckit/scripts/
# Keep your project data
# projects/ directory stays (user data)
Note: The ArcKit CLI no longer distributes Claude Code commands. Claude Code users should use this plugin instead.
This plugin is for Claude Code. For other AI assistants:
gemini extensions install https://github.com/tractorjuice/arckit-gemini)uv tool install arckit-cli --from git+https://github.com/tractorjuice/arc-kit.git, then arckit init --ai codex)MIT
hooks/mod/register.mjs 144 lines1/**
2 * ArcKit status band: a Claude Code mod (hooks module).
3 *
4 * Draws one line above the prompt in an ArcKit repository (found by walking up
5 * from the session's folder, as findRepoRoot does, so a session started inside
6 * projects/ or a project still sees it): how many projects
7 * and artefacts there are, how many are DRAFT, and how many reviews are
8 * overdue, with a pointer to /arckit:health when something needs attention.
9 * In the terminal it starts counting when the session starts; the desktop
10 * app's Code tab joins its session later, so there it starts on session.attach.
11 *
12 * Claude Code only, and additive. It needs Claude Code v2.1.287+ (mods on by
13 * default); an older client never loads it, and the classic hooks in
14 * hooks.json run beside it unchanged. It only observes: every tool.call hook
15 * passes the call on untouched, so no gate depends on it. It lives in a
16 * subdirectory so the converter's hooks/*.mjs copy for Kimi never ships it.
17 *
18 * Set ARCKIT_NO_STATUS_BAND to switch it off.
19 *
20 * Counting rules live in status-model.mjs (pure, tested by
21 * tests/plugin/status-band.test.mjs).
22 */
23
24import { bandText, candidateDirs, isArtefactName, isProjectDir, isProjectsListing, needsAttention, summarise } from './status-model.mjs';
25
26const MAX_DEPTH = 4;
27const MAX_FILES = 2000;
28const MAX_BYTES = 4 * 1024 * 1024;
29// The surfaces Claude Code raises the AbovePrompt band on.
30const BAND_SURFACES = new Set(['terminal', 'desktop']);
31
32function localDate(ms) {
33 const at = new Date(ms);
34 const pad = (n) => String(n).padStart(2, '0');
35 return `${at.getFullYear()}-${pad(at.getMonth() + 1)}-${pad(at.getDate())}`;
36}
37
38let projectsDir = null;
39let summary = null;
40let isDirty = false;
41let scanning = null;
42
43async function collect($, dir, depth, found) {
44 if (depth > MAX_DEPTH || found.length >= MAX_FILES) return;
45 const entries = await $.fs.list(dir).catch(() => []);
46 for (const entry of entries) {
47 if (entry.isLink || found.length >= MAX_FILES) continue;
48 if (entry.kind === 'dir' && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
49 await collect($, `${dir}/${entry.name}`, depth + 1, found);
50 } else if (entry.kind === 'file' && isArtefactName(entry.name) && entry.size <= MAX_BYTES) {
51 found.push(`${dir}/${entry.name}`);
52 }
53 }
54}
55
56async function scan($) {
57 const top = await $.fs.list(projectsDir);
58 const projectNames = top.filter((e) => e.kind === 'dir' && isProjectDir(e.name)).map((e) => e.name);
59 const paths = [];
60 for (const name of projectNames) {
61 await collect($, `${projectsDir}/${name}`, 0, paths);
62 }
63 const texts = await Promise.all(paths.map((path) => $.fs.read(path).catch(() => '')));
64 summary = summarise(projectNames, texts, localDate(await $.clock.now()));
65 $.ui.invalidate('ui.render');
66}
67
68function rescan($) {
69 if (!projectsDir || scanning) return;
70 isDirty = false;
71 scanning = scan($)
72 .catch(() => {
73 summary = null;
74 })
75 .finally(() => {
76 scanning = null;
77 });
78}
79
80async function findProjectsDir($, cwd) {
81 for (const candidate of candidateDirs(cwd)) {
82 const entries = await $.fs.list(candidate).catch(() => null);
83 if (entries && isProjectsListing(entries)) return candidate;
84 }
85 return null;
86}
87
88async function start($, cwd) {
89 if ((await $.env.get('ARCKIT_NO_STATUS_BAND')) !== undefined) return;
90 if (!projectsDir) projectsDir = await findProjectsDir($, cwd);
91 if (projectsDir) rescan($);
92}
93
94async function onSessionStart($, e, next) {
95 const result = await next(e);
96 if (e.isInteractive && e.surface === 'terminal') await start($, e.cwd);
97 return result;
98}
99
100// The desktop app runs the session headless and joins it afterwards, so its
101// session.start says no surface; it arrives here instead, before it first draws.
102async function onSessionAttach($, e, next) {
103 const result = await next(e);
104 if (BAND_SURFACES.has(e.surface)) await start($, await $.session.cwd());
105 return result;
106}
107
108async function markDirty($, e, next) {
109 const result = await next(e);
110 if (projectsDir) isDirty = true;
111 return result;
112}
113
114async function onTurnComplete($, e, next) {
115 const result = await next(e);
116 if (isDirty) rescan($);
117 return result;
118}
119
120async function drawBand($, e, next) {
121 if (!summary || summary.projects === 0 || e.props.hasSurvey) return next(e);
122 const { Box, Text } = await $.ui.resolve(e);
123 const text = bandText(summary);
124 return Box({
125 paddingX: 1,
126 children: [
127 needsAttention(summary)
128 ? Text({ color: 'yellow', wrap: 'truncate-end', children: text })
129 : Text({ dimColor: true, wrap: 'truncate-end', children: text }),
130 ],
131 });
132}
133
134/** @type {import('claude-code').Register} */
135export function register(on) {
136 on('session.start', onSessionStart);
137 on('session.attach', onSessionAttach);
138 on('tool.call', { tool: 'Write' }, markDirty);
139 on('tool.call', { tool: 'Edit' }, markDirty);
140 on('tool.call', { tool: 'Bash' }, markDirty);
141 on('turn.complete', onTurnComplete);
142 on('ui.render', { component: 'AbovePrompt' }, drawBand);
143}
144hooks/mod/status-model.mjs 133 lines1/**
2 * ArcKit status band: the pure half.
3 *
4 * Turns the artefacts under projects/ into the one line the status-band mod
5 * draws above the prompt. No I/O and no Node imports, so the mod (which runs
6 * in Claude Code's mods sandbox, with no Node) and `node --test` both load it.
7 *
8 * The DRAFT and review-overdue rules match detect-stale-artifacts.sh and the
9 * STALE-DRAFT / REVIEW-OVERDUE rules in graph-inject.mjs formatHealth, so the
10 * band never disagrees with /arckit:health.
11 */
12
13export const STALE_DRAFT_DAYS = 30;
14
15const ISO_DATE = /\d{4}-\d{2}-\d{2}/;
16const STATUSES = /(DRAFT|IN_REVIEW|APPROVED|PUBLISHED|SUPERSEDED|ARCHIVED)/i;
17
18export function isArtefactName(name) {
19 return /^ARC-.+\.md$/.test(name);
20}
21
22export function isProjectDir(name) {
23 return /^\d{3}-/.test(name);
24}
25
26/**
27 * Whether a directory listing is an ArcKit projects/ folder: it holds at
28 * least one numbered project directory. Matches findRepoRoot in
29 * hook-utils.mjs, so the band finds the same folder the other hooks do.
30 */
31export function isProjectsListing(entries) {
32 return entries.some((e) => e.kind === 'dir' && /^\d{3}(?:-|$)/.test(e.name));
33}
34
35/**
36 * The folders to try for projects/, from the session's folder up to the
37 * filesystem root, so a session started inside projects/ or a project still
38 * finds it. Handles both / and \ separators.
39 */
40export function candidateDirs(cwd, limit = 32) {
41 const out = [];
42 let dir = String(cwd).replace(/[\\/]+$/, '');
43 for (let i = 0; i < limit && dir; i += 1) {
44 out.push(`${dir}/projects`);
45 const cut = Math.max(dir.lastIndexOf('/'), dir.lastIndexOf('\\'));
46 if (cut <= 0) {
47 if (cut === 0 && dir.length > 1) out.push('/projects');
48 break;
49 }
50 dir = dir.slice(0, cut);
51 if (/^[A-Za-z]:$/.test(dir)) {
52 out.push(`${dir}/projects`);
53 break;
54 }
55 }
56 return out;
57}
58
59function dateOnRow(lines, label) {
60 const row = lines.find((line) => label.test(line));
61 const match = row && row.match(ISO_DATE);
62 return match ? match[0] : null;
63}
64
65/**
66 * The Document Control facts the band counts. Status is read only from a row
67 * labelled exactly "Status" (bold allowed), so an entity table further down
68 * the file cannot be mistaken for it.
69 */
70export function documentFacts(text) {
71 const lines = String(text).split('\n');
72 const statusRow = lines.find((line) => /^\|\s*\*{0,2}\s*Status\s*\*{0,2}\s*\|/i.test(line));
73 const statusMatch = statusRow && statusRow.match(STATUSES);
74 return {
75 status: statusMatch ? statusMatch[1].toUpperCase() : null,
76 nextReview: dateOnRow(lines, /Next Review Date/i),
77 lastModified: dateOnRow(lines, /Last Modified/i),
78 };
79}
80
81export function daysBefore(isoDate, days) {
82 const [y, m, d] = isoDate.split('-').map(Number);
83 const at = new Date(Date.UTC(y, m - 1, d - days));
84 return at.toISOString().slice(0, 10);
85}
86
87/**
88 * @param {string[]} projectNames the NNN-name directories under projects/
89 * @param {string[]} texts each artefact's content
90 * @param {string} today YYYY-MM-DD
91 */
92export function summarise(projectNames, texts, today) {
93 const staleBefore = daysBefore(today, STALE_DRAFT_DAYS);
94 const summary = { projects: projectNames.length, artefacts: texts.length, draft: 0, staleDraft: 0, overdue: 0 };
95 for (const text of texts) {
96 const facts = documentFacts(text);
97 if (facts.nextReview && facts.nextReview < today) {
98 summary.overdue += 1;
99 }
100 if (facts.status === 'DRAFT') {
101 summary.draft += 1;
102 if (facts.lastModified && facts.lastModified < staleBefore) {
103 summary.staleDraft += 1;
104 }
105 }
106 }
107 return summary;
108}
109
110export function needsAttention(summary) {
111 return summary.overdue > 0 || summary.staleDraft > 0;
112}
113
114function plural(count, one, many = `${one}s`) {
115 return `${count} ${count === 1 ? one : many}`;
116}
117
118export function bandText(summary) {
119 const parts = [
120 'ArcKit',
121 plural(summary.projects, 'project'),
122 plural(summary.artefacts, 'artefact'),
123 ];
124 if (summary.draft > 0) {
125 parts.push(summary.staleDraft > 0 ? `${summary.draft} draft (${summary.staleDraft} stale)` : `${summary.draft} draft`);
126 }
127 if (summary.overdue > 0) {
128 parts.push(`${plural(summary.overdue, 'review')} overdue`);
129 }
130 const line = parts.join(' · ');
131 return needsAttention(summary) ? `${line} — run /arckit:health` : line;
132}
133