harness-bujang 0.1.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/LICENSE +21 -0
- package/README.md +87 -0
- package/dist/index.js +906 -0
- package/package.json +57 -0
- package/templates/agents/en/architect-team.md +105 -0
- package/templates/agents/en/code-review-team.md +106 -0
- package/templates/agents/en/consultant.md +106 -0
- package/templates/agents/en/db-guard-team.md +94 -0
- package/templates/agents/en/dev-team.md +143 -0
- package/templates/agents/en/director.md +401 -0
- package/templates/agents/en/doc-sync-team.md +92 -0
- package/templates/agents/en/qa-team.md +100 -0
- package/templates/agents/en/security-team.md +99 -0
- package/templates/agents/en/verifier-team.md +97 -0
- package/templates/agents/ko/architect-team.md +110 -0
- package/templates/agents/ko/code-review-team.md +124 -0
- package/templates/agents/ko/consultant.md +106 -0
- package/templates/agents/ko/db-guard-team.md +116 -0
- package/templates/agents/ko/dev-team.md +144 -0
- package/templates/agents/ko/director.md +401 -0
- package/templates/agents/ko/doc-sync-team.md +114 -0
- package/templates/agents/ko/qa-team.md +122 -0
- package/templates/agents/ko/security-team.md +121 -0
- package/templates/agents/ko/verifier-team.md +119 -0
- package/templates/project-template/app/admin/harness/harness-client.tsx +493 -0
- package/templates/project-template/app/admin/harness/page.tsx +27 -0
- package/templates/project-template/app/api/harness/logs/route.ts +111 -0
- package/templates/project-template/app/api/harness/reply/route.ts +45 -0
- package/templates/project-template/lib/harness-db/index.ts +45 -0
- package/templates/project-template/lib/harness-db/sqlite.ts +128 -0
- package/templates/project-template/lib/harness-db/supabase.ts +64 -0
- package/templates/project-template/lib/harness-db/types.ts +35 -0
- package/templates/project-template/migrations/00010_harness_messages.sql +56 -0
- package/templates/project-template/migrations/00025_harness_insert_admin_only.sql +17 -0
- package/templates/templates/en/AGENT_LEARNING_LOG.seed.md +43 -0
- package/templates/templates/en/CLAUDE.md.harness-section.template +59 -0
- package/templates/templates/ko/AGENT_LEARNING_LOG.seed.md +43 -0
- package/templates/templates/ko/CLAUDE.md.harness-section.template +59 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Factory + singleton for the active harness-db adapter.
|
|
2
|
+
//
|
|
3
|
+
// Mode is decided once per process by the env var HARNESS_DB:
|
|
4
|
+
// - "sqlite" (default) — local file `.harness/chat.db`, no setup
|
|
5
|
+
// - "supabase" — cloud Postgres via Supabase, prod-ready
|
|
6
|
+
//
|
|
7
|
+
// To switch later, run `bujang migrate --to=supabase` (or back to sqlite).
|
|
8
|
+
|
|
9
|
+
import type { HarnessDb, HarnessDbMode } from './types';
|
|
10
|
+
import { createSqliteAdapter } from './sqlite';
|
|
11
|
+
import { createSupabaseAdapter } from './supabase';
|
|
12
|
+
|
|
13
|
+
export type { HarnessDb, ChatMessage, ListOptions, HarnessDbMode } from './types';
|
|
14
|
+
export { createSqliteAdapter, createSupabaseAdapter };
|
|
15
|
+
|
|
16
|
+
let _instance: HarnessDb | null = null;
|
|
17
|
+
|
|
18
|
+
export function getHarnessDb(): HarnessDb {
|
|
19
|
+
if (_instance) return _instance;
|
|
20
|
+
_instance = createAdapter(currentMode());
|
|
21
|
+
return _instance;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function currentMode(): HarnessDbMode {
|
|
25
|
+
const m = (process.env.HARNESS_DB ?? 'sqlite').toLowerCase();
|
|
26
|
+
if (m === 'sqlite' || m === 'supabase') return m;
|
|
27
|
+
throw new Error(
|
|
28
|
+
`harness-db: invalid HARNESS_DB="${m}". Expected "sqlite" or "supabase".`,
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function createAdapter(mode: HarnessDbMode): HarnessDb {
|
|
33
|
+
switch (mode) {
|
|
34
|
+
case 'sqlite': return createSqliteAdapter();
|
|
35
|
+
case 'supabase': return createSupabaseAdapter();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Reset the cached singleton. Mainly for tests / migrations that need to
|
|
41
|
+
* switch adapters mid-process.
|
|
42
|
+
*/
|
|
43
|
+
export function resetHarnessDb(): void {
|
|
44
|
+
_instance = null;
|
|
45
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// SQLite adapter for harness-db.
|
|
2
|
+
//
|
|
3
|
+
// Storage: `.harness/chat.db` in the project root by default.
|
|
4
|
+
// Override via env var `HARNESS_SQLITE_PATH=/absolute/path/to/chat.db`.
|
|
5
|
+
//
|
|
6
|
+
// Requires `better-sqlite3` to be installed in the host project:
|
|
7
|
+
// npm i better-sqlite3
|
|
8
|
+
// npm i -D @types/better-sqlite3
|
|
9
|
+
|
|
10
|
+
import * as path from 'node:path';
|
|
11
|
+
import * as fs from 'node:fs';
|
|
12
|
+
import type { HarnessDb, ChatMessage, ListOptions } from './types';
|
|
13
|
+
|
|
14
|
+
let _db: import('better-sqlite3').Database | null = null;
|
|
15
|
+
|
|
16
|
+
function resolveDbPath(): string {
|
|
17
|
+
if (process.env.HARNESS_SQLITE_PATH) return process.env.HARNESS_SQLITE_PATH;
|
|
18
|
+
return path.join(process.cwd(), '.harness', 'chat.db');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function ensureDb(): Promise<import('better-sqlite3').Database> {
|
|
22
|
+
if (_db) return _db;
|
|
23
|
+
|
|
24
|
+
// Lazy import so projects that don't use SQLite don't pay the cost.
|
|
25
|
+
const { default: Database } = await import('better-sqlite3');
|
|
26
|
+
|
|
27
|
+
const dbPath = resolveDbPath();
|
|
28
|
+
const dir = path.dirname(dbPath);
|
|
29
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
30
|
+
|
|
31
|
+
_db = new Database(dbPath);
|
|
32
|
+
_db.pragma('journal_mode = WAL');
|
|
33
|
+
|
|
34
|
+
_db.exec(`
|
|
35
|
+
CREATE TABLE IF NOT EXISTS harness_messages (
|
|
36
|
+
id TEXT PRIMARY KEY,
|
|
37
|
+
timestamp TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
38
|
+
"from" TEXT NOT NULL,
|
|
39
|
+
"to" TEXT NOT NULL,
|
|
40
|
+
type TEXT NOT NULL CHECK (type IN ('command', 'feedback', 'info', 'report')),
|
|
41
|
+
message TEXT NOT NULL,
|
|
42
|
+
severity TEXT CHECK (severity IS NULL OR severity IN ('info', 'warning', 'error')),
|
|
43
|
+
data TEXT,
|
|
44
|
+
created_at TEXT DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
CREATE INDEX IF NOT EXISTS harness_messages_timestamp_idx
|
|
48
|
+
ON harness_messages(timestamp DESC);
|
|
49
|
+
|
|
50
|
+
CREATE INDEX IF NOT EXISTS harness_messages_from_to_idx
|
|
51
|
+
ON harness_messages("from", "to");
|
|
52
|
+
`);
|
|
53
|
+
|
|
54
|
+
return _db;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function rowToMessage(row: any): ChatMessage {
|
|
58
|
+
return {
|
|
59
|
+
id: row.id,
|
|
60
|
+
timestamp: row.timestamp,
|
|
61
|
+
from: row.from,
|
|
62
|
+
to: row.to,
|
|
63
|
+
type: row.type,
|
|
64
|
+
message: row.message,
|
|
65
|
+
...(row.severity ? { severity: row.severity } : {}),
|
|
66
|
+
...(row.data ? { data: JSON.parse(row.data) } : {}),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function createSqliteAdapter(): HarnessDb {
|
|
71
|
+
return {
|
|
72
|
+
async list(options: ListOptions = {}) {
|
|
73
|
+
const db = await ensureDb();
|
|
74
|
+
|
|
75
|
+
if (options.before) {
|
|
76
|
+
const limit = options.limit ?? 50;
|
|
77
|
+
const stmt = db.prepare(
|
|
78
|
+
`SELECT * FROM harness_messages
|
|
79
|
+
WHERE timestamp < ?
|
|
80
|
+
ORDER BY timestamp ASC
|
|
81
|
+
LIMIT ?`,
|
|
82
|
+
);
|
|
83
|
+
return stmt.all(options.before, limit).map(rowToMessage);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const days = options.days ?? 2;
|
|
87
|
+
const since = new Date(Date.now() - days * 86_400_000).toISOString();
|
|
88
|
+
const stmt = db.prepare(
|
|
89
|
+
`SELECT * FROM harness_messages
|
|
90
|
+
WHERE timestamp >= ?
|
|
91
|
+
ORDER BY timestamp ASC`,
|
|
92
|
+
);
|
|
93
|
+
return stmt.all(since).map(rowToMessage);
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
async upsert(msg: ChatMessage) {
|
|
97
|
+
const db = await ensureDb();
|
|
98
|
+
const stmt = db.prepare(
|
|
99
|
+
`INSERT INTO harness_messages (id, timestamp, "from", "to", type, message, severity, data)
|
|
100
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
101
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
102
|
+
timestamp = excluded.timestamp,
|
|
103
|
+
"from" = excluded."from",
|
|
104
|
+
"to" = excluded."to",
|
|
105
|
+
type = excluded.type,
|
|
106
|
+
message = excluded.message,
|
|
107
|
+
severity = excluded.severity,
|
|
108
|
+
data = excluded.data`,
|
|
109
|
+
);
|
|
110
|
+
stmt.run(
|
|
111
|
+
msg.id,
|
|
112
|
+
msg.timestamp,
|
|
113
|
+
msg.from,
|
|
114
|
+
msg.to,
|
|
115
|
+
msg.type,
|
|
116
|
+
msg.message,
|
|
117
|
+
msg.severity ?? null,
|
|
118
|
+
msg.data ? JSON.stringify(msg.data) : null,
|
|
119
|
+
);
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
async count() {
|
|
123
|
+
const db = await ensureDb();
|
|
124
|
+
const row = db.prepare('SELECT COUNT(*) AS c FROM harness_messages').get() as { c: number };
|
|
125
|
+
return row.c;
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Supabase adapter for harness-db.
|
|
2
|
+
//
|
|
3
|
+
// Required env vars:
|
|
4
|
+
// NEXT_PUBLIC_SUPABASE_URL
|
|
5
|
+
// SUPABASE_SERVICE_ROLE_KEY
|
|
6
|
+
//
|
|
7
|
+
// Apply migrations 00010_harness_messages.sql and 00025_harness_insert_admin_only.sql
|
|
8
|
+
// against your Supabase project before using.
|
|
9
|
+
|
|
10
|
+
import { createClient, type SupabaseClient } from '@supabase/supabase-js';
|
|
11
|
+
import type { HarnessDb, ChatMessage, ListOptions } from './types';
|
|
12
|
+
|
|
13
|
+
let _client: SupabaseClient | null = null;
|
|
14
|
+
|
|
15
|
+
function client(): SupabaseClient {
|
|
16
|
+
if (_client) return _client;
|
|
17
|
+
const url = process.env.NEXT_PUBLIC_SUPABASE_URL;
|
|
18
|
+
const key = process.env.SUPABASE_SERVICE_ROLE_KEY;
|
|
19
|
+
if (!url || !key) {
|
|
20
|
+
throw new Error(
|
|
21
|
+
'harness-db (supabase): NEXT_PUBLIC_SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY are required',
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
_client = createClient(url, key);
|
|
25
|
+
return _client;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function createSupabaseAdapter(): HarnessDb {
|
|
29
|
+
return {
|
|
30
|
+
async list(options: ListOptions = {}) {
|
|
31
|
+
let q = client()
|
|
32
|
+
.from('harness_messages')
|
|
33
|
+
.select('*')
|
|
34
|
+
.order('timestamp', { ascending: true });
|
|
35
|
+
|
|
36
|
+
if (options.before) {
|
|
37
|
+
q = q.lt('timestamp', options.before).limit(options.limit ?? 50);
|
|
38
|
+
} else {
|
|
39
|
+
const days = options.days ?? 2;
|
|
40
|
+
const since = new Date(Date.now() - days * 86_400_000).toISOString();
|
|
41
|
+
q = q.gte('timestamp', since);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const { data, error } = await q;
|
|
45
|
+
if (error) throw new Error(error.message);
|
|
46
|
+
return (data ?? []) as ChatMessage[];
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
async upsert(msg: ChatMessage) {
|
|
50
|
+
const { error } = await client()
|
|
51
|
+
.from('harness_messages')
|
|
52
|
+
.upsert(msg, { onConflict: 'id' });
|
|
53
|
+
if (error) throw new Error(error.message);
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
async count() {
|
|
57
|
+
const { count, error } = await client()
|
|
58
|
+
.from('harness_messages')
|
|
59
|
+
.select('id', { count: 'exact', head: true });
|
|
60
|
+
if (error) throw new Error(error.message);
|
|
61
|
+
return count ?? 0;
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Shared types for the harness-db adapters (sqlite + supabase).
|
|
2
|
+
// Both adapters implement the same `HarnessDb` interface so the API routes
|
|
3
|
+
// don't care which one is active — the choice is made by the HARNESS_DB env var.
|
|
4
|
+
|
|
5
|
+
export interface ChatMessage {
|
|
6
|
+
id: string;
|
|
7
|
+
/** ISO 8601 timestamp string (UTC). */
|
|
8
|
+
timestamp: string;
|
|
9
|
+
from: string;
|
|
10
|
+
to: string;
|
|
11
|
+
type: 'command' | 'feedback' | 'info' | 'report';
|
|
12
|
+
message: string;
|
|
13
|
+
severity?: 'info' | 'warning' | 'error';
|
|
14
|
+
data?: Record<string, unknown>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ListOptions {
|
|
18
|
+
/** When set, return messages newer than `now() - days`. Mutually exclusive with `before`. */
|
|
19
|
+
days?: number;
|
|
20
|
+
/** When set, return messages older than this ISO timestamp. Used for infinite scroll. */
|
|
21
|
+
before?: string;
|
|
22
|
+
/** Used together with `before`. Defaults to 50. */
|
|
23
|
+
limit?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface HarnessDb {
|
|
27
|
+
/** List messages. With `days`: recent N days. With `before`: older messages (paginated). */
|
|
28
|
+
list(options?: ListOptions): Promise<ChatMessage[]>;
|
|
29
|
+
/** Insert one message. Idempotent on `id`. */
|
|
30
|
+
upsert(message: ChatMessage): Promise<void>;
|
|
31
|
+
/** Total count — used for generating the next sequential id. */
|
|
32
|
+
count(): Promise<number>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type HarnessDbMode = 'sqlite' | 'supabase';
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
-- harness_messages: chat-room messages for the Harness-Bujang multi-agent system
|
|
2
|
+
--
|
|
3
|
+
-- This migration assumes Postgres + a `users` table with a `role` column ('admin' / etc.).
|
|
4
|
+
-- If your schema differs, replace the `EXISTS (...)` predicates with your own admin check.
|
|
5
|
+
--
|
|
6
|
+
-- Idempotent: uses CREATE TABLE IF NOT EXISTS + DROP POLICY IF EXISTS.
|
|
7
|
+
|
|
8
|
+
CREATE TABLE IF NOT EXISTS public.harness_messages (
|
|
9
|
+
id text PRIMARY KEY,
|
|
10
|
+
"timestamp" timestamptz NOT NULL DEFAULT now(),
|
|
11
|
+
"from" text NOT NULL,
|
|
12
|
+
"to" text NOT NULL,
|
|
13
|
+
type text NOT NULL CHECK (type IN ('command', 'feedback', 'info', 'report')),
|
|
14
|
+
message text NOT NULL,
|
|
15
|
+
severity text CHECK (severity IS NULL OR severity IN ('info', 'warning', 'error')),
|
|
16
|
+
data jsonb,
|
|
17
|
+
created_at timestamptz DEFAULT now()
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
CREATE INDEX IF NOT EXISTS harness_messages_timestamp_idx
|
|
21
|
+
ON public.harness_messages ("timestamp" DESC);
|
|
22
|
+
|
|
23
|
+
CREATE INDEX IF NOT EXISTS harness_messages_from_to_idx
|
|
24
|
+
ON public.harness_messages ("from", "to");
|
|
25
|
+
|
|
26
|
+
-- Enable Row Level Security
|
|
27
|
+
ALTER TABLE public.harness_messages ENABLE ROW LEVEL SECURITY;
|
|
28
|
+
|
|
29
|
+
-- ---------------------------------------------------------------------------
|
|
30
|
+
-- Policies
|
|
31
|
+
-- ---------------------------------------------------------------------------
|
|
32
|
+
-- Admins can SELECT all messages.
|
|
33
|
+
-- Adjust the EXISTS subquery below to match your project's admin definition.
|
|
34
|
+
|
|
35
|
+
DROP POLICY IF EXISTS admin_select_harness_messages ON public.harness_messages;
|
|
36
|
+
CREATE POLICY admin_select_harness_messages
|
|
37
|
+
ON public.harness_messages FOR SELECT
|
|
38
|
+
USING (
|
|
39
|
+
EXISTS (
|
|
40
|
+
SELECT 1 FROM public.users
|
|
41
|
+
WHERE users.id = auth.uid()
|
|
42
|
+
AND users.role = 'admin'
|
|
43
|
+
)
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
-- Initial INSERT policy (further hardened by 00025).
|
|
47
|
+
DROP POLICY IF EXISTS admin_insert_harness_messages ON public.harness_messages;
|
|
48
|
+
CREATE POLICY admin_insert_harness_messages
|
|
49
|
+
ON public.harness_messages FOR INSERT
|
|
50
|
+
WITH CHECK (
|
|
51
|
+
EXISTS (
|
|
52
|
+
SELECT 1 FROM public.users
|
|
53
|
+
WHERE users.id = auth.uid()
|
|
54
|
+
AND users.role = 'admin'
|
|
55
|
+
)
|
|
56
|
+
);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
-- Harden the harness_messages INSERT policy to admin-only.
|
|
2
|
+
--
|
|
3
|
+
-- This replaces any earlier permissive INSERT policy (e.g., authenticated-only)
|
|
4
|
+
-- with a strict admin check. Run this only after 00010.
|
|
5
|
+
|
|
6
|
+
DROP POLICY IF EXISTS harness_admin_insert ON public.harness_messages;
|
|
7
|
+
|
|
8
|
+
CREATE POLICY harness_admin_insert
|
|
9
|
+
ON public.harness_messages
|
|
10
|
+
FOR INSERT TO authenticated
|
|
11
|
+
WITH CHECK (
|
|
12
|
+
EXISTS (
|
|
13
|
+
SELECT 1 FROM public.users
|
|
14
|
+
WHERE users.id = auth.uid()
|
|
15
|
+
AND users.role = 'admin'
|
|
16
|
+
)
|
|
17
|
+
);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Agent Learning Log
|
|
2
|
+
|
|
3
|
+
> Shared record of mistakes and lessons across all teams (Director, dev-team, auditors, verifier-team, consultant).
|
|
4
|
+
> **Mandatory reading at session start.** Append new entries at the bottom (preserve insertion order; don't reverse).
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 📐 Format
|
|
9
|
+
|
|
10
|
+
```markdown
|
|
11
|
+
### YYYY-MM-DD — [team] one-line title
|
|
12
|
+
|
|
13
|
+
**Context**: 1–2 lines on what was being done
|
|
14
|
+
**Mistake / misjudgment**: what went wrong
|
|
15
|
+
**Root cause**: why it happened (structural or cognitive)
|
|
16
|
+
**Lesson**: how to do it differently next time
|
|
17
|
+
**Files**: file:line (if any)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 📚 Entries
|
|
23
|
+
|
|
24
|
+
<!-- First entry is added by the init script, or appended by the Director after the first task. -->
|
|
25
|
+
|
|
26
|
+
### {{TODAY}} — [Director] Harness-Bujang adopted
|
|
27
|
+
|
|
28
|
+
**Context**: Introduced the Harness-Bujang multi-agent system to this project.
|
|
29
|
+
**Root cause**: Single-agent work has weak review and zero chat visibility — when something stalls, the principal cannot see where.
|
|
30
|
+
**Lesson**: Never skip `{{HARNESS_TABLE}}` INSERTs. Every step must surface in the chat room so the principal can track progress.
|
|
31
|
+
**Files**: `.claude/agents/*.md`, `{{ADMIN_HARNESS_ROUTE}}`
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 🎯 Recurring categories (high-attention areas)
|
|
36
|
+
|
|
37
|
+
Mistakes in these categories must update **both this log AND the relevant agent file** on recurrence:
|
|
38
|
+
|
|
39
|
+
- DB schema judgment — migration files vs. actual prod schema drift
|
|
40
|
+
- Prose reports — chat INSERTs without markdown bullets
|
|
41
|
+
- Missing chat INSERTs — work step completed but never logged
|
|
42
|
+
- Skipped verification — reporting "done" without going through `verifier-team`
|
|
43
|
+
- Missing audit calls — payment / DB / legal-text work without the relevant audit team
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Harness-Bujang — section template appended to the user's CLAUDE.md.
|
|
3
|
+
The init script reads this file, fills `{{...}}` placeholders, and appends it.
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
## Harness Engineering (Agent Organization)
|
|
7
|
+
|
|
8
|
+
### Structure
|
|
9
|
+
|
|
10
|
+
- **Entry point**: Claude Code CLI only. The chat room is observation-only.
|
|
11
|
+
- **Director = a persona of Main Claude** 🎭 (not a real subagent)
|
|
12
|
+
- Claude Code platform constraint: subagents cannot spawn other subagents
|
|
13
|
+
- Real team calls and code work are done **by Main Claude directly**
|
|
14
|
+
- Main Claude reads `.claude/agents/director.md` and takes on the Director's tone and responsibilities
|
|
15
|
+
- Chat-room INSERTs are **proxied** by Main Claude under the Director's / each team's name
|
|
16
|
+
- **Actual subagents**: `.claude/agents/*.md` — invoked by Main Claude via the `Agent` tool
|
|
17
|
+
- `dev-team` · `architect-team` · `doc-sync-team` · `code-review-team` · `security-team` · `db-guard-team` · `qa-team` · `verifier-team` · `consultant`
|
|
18
|
+
- **Chat room**: `{{ADMIN_HARNESS_ROUTE}}` — super-admin only (`SUPER_ADMIN_EMAILS` env). Real-time view of `{{HARNESS_TABLE}}`.
|
|
19
|
+
- **Learning log**: `{{LEARNING_LOG_PATH}}` — collective record of mistakes and lessons. Mandatory reading at session start.
|
|
20
|
+
- **Chat message format**: markdown line breaks and bullets required. No prose.
|
|
21
|
+
|
|
22
|
+
### Flow
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
Principal's command
|
|
26
|
+
↓
|
|
27
|
+
Main Claude (= Director persona)
|
|
28
|
+
├─ INSERT into chat as 'director' (receive, plan, dispatch)
|
|
29
|
+
├─ Call Agent(dev-team) — Main Claude does this directly
|
|
30
|
+
├─ INSERT as 'dev-team' (proxy completion report)
|
|
31
|
+
├─ Call Agent(code-review-team / security-team / ...) in parallel
|
|
32
|
+
├─ INSERT each team's result on its behalf
|
|
33
|
+
├─ Call Agent(verifier-team) as the final gate
|
|
34
|
+
├─ INSERT as 'director' (final report)
|
|
35
|
+
└─ Reply to the principal
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Every step writes to `{{HARNESS_TABLE}}` → live in the chat-room UI.
|
|
39
|
+
|
|
40
|
+
### 🎭 Director persona rules
|
|
41
|
+
|
|
42
|
+
- **Tone**: polite professional. To the principal "…done"; to teams "please review."
|
|
43
|
+
- **Identity**: Main Claude plays the Director by following `.claude/agents/director.md` — its scope, checklists, and work-mapping table.
|
|
44
|
+
- **Persona guide**: `.claude/agents/director.md` is the only source.
|
|
45
|
+
|
|
46
|
+
### 🚨 Real-time chat reporting — absolute rule
|
|
47
|
+
|
|
48
|
+
Every major step must INSERT into `public.{{HARNESS_TABLE}}`. Main Claude proxies each role:
|
|
49
|
+
|
|
50
|
+
1. Right after receiving — `from='principal' to='director' type='command'`
|
|
51
|
+
2. On dispatch — `from='director' to='dev-team' type='command'`
|
|
52
|
+
3. Team completion — `from='dev-team' to='director' type='report'`
|
|
53
|
+
4. Final report — `from='director' to='principal' type='report'`
|
|
54
|
+
5. Failures / blockers — `severity='warning'` or higher, immediately
|
|
55
|
+
|
|
56
|
+
Table columns: `id · timestamp · from · to · type · message · severity · data · created_at`
|
|
57
|
+
type CHECK: `command|feedback|info|report`
|
|
58
|
+
Message format: markdown bullets (no prose)
|
|
59
|
+
Details: `.claude/agents/director.md` · `{{LEARNING_LOG_PATH}}`
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# 에이전트 집단 학습 로그
|
|
2
|
+
|
|
3
|
+
> 모든 팀(부장·dev-team·감사팀·검수팀·consultant)이 공유하는 실수·교훈 기록.
|
|
4
|
+
> **세션 시작 시 필독.** 새 항목은 하단에 append (시간 역순 X — 추가 순서 보존).
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 📐 형식
|
|
9
|
+
|
|
10
|
+
```markdown
|
|
11
|
+
### YYYY-MM-DD — [팀명] 한 줄 제목
|
|
12
|
+
|
|
13
|
+
**상황**: 무엇을 하다가 발생했는지 1~2줄
|
|
14
|
+
**실수/오판**: 무엇이 잘못됐는지
|
|
15
|
+
**원인**: 왜 그렇게 됐는지 (구조적·인지적)
|
|
16
|
+
**교훈**: 다음에 어떻게 다르게 할 것인지
|
|
17
|
+
**파일**: 관련 파일:라인 (있으면)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 📚 항목
|
|
23
|
+
|
|
24
|
+
<!-- 첫 항목은 init 스크립트가 자동으로 추가하거나, 첫 작업 후 부장이 append 한다. -->
|
|
25
|
+
|
|
26
|
+
### {{TODAY}} — [부장] 하네스 부장 시스템 도입
|
|
27
|
+
|
|
28
|
+
**상황**: 본 프로젝트에 하네스 부장 다중 에이전트 시스템 도입.
|
|
29
|
+
**원인**: 코드 작업이 단일 에이전트로는 검수가 약하고, 톡방 가시성 없이는 "어디서 막혔는지" 파악이 어려움.
|
|
30
|
+
**교훈**: 작업마다 `{{HARNESS_TABLE}}` INSERT를 빠뜨리지 말 것. 모든 단계가 톡방에 보여야 대표님이 진행 파악 가능.
|
|
31
|
+
**파일**: `.claude/agents/*.md`, `{{ADMIN_HARNESS_ROUTE}}`
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 🎯 자주 반복되는 카테고리 (주의 영역)
|
|
36
|
+
|
|
37
|
+
이 카테고리에 해당하는 실수는 **재발 시 즉시 본 로그 + 해당 팀 에이전트 파일 둘 다 갱신**:
|
|
38
|
+
|
|
39
|
+
- DB 스키마 판단 — 마이그레이션 파일 vs prod 실태 불일치
|
|
40
|
+
- 줄글 보고 — 톡방에 마크다운 없이 한 줄 줄글 INSERT
|
|
41
|
+
- 톡방 INSERT 누락 — 작업 단계 진행했는데 기록 안 함
|
|
42
|
+
- 검수 누락 — verifier-team 거치지 않고 "완료" 보고
|
|
43
|
+
- 감사팀 누락 — 결제·DB·법적 문구 등 도메인 작업에 해당 감사팀 호출 안 함
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
하네스 부장 (Harness-Bujang) — 사용자의 CLAUDE.md 에 추가되는 섹션 템플릿.
|
|
3
|
+
init 스크립트가 이 파일을 읽어 `{{...}}` placeholder를 채운 뒤 본 프로젝트 CLAUDE.md 하단에 append 한다.
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
## 하네스 엔지니어링 (에이전트 조직)
|
|
7
|
+
|
|
8
|
+
### 구조
|
|
9
|
+
|
|
10
|
+
- **명령 입구**: Claude Code CLI만. 톡방은 관찰 전용
|
|
11
|
+
- **부장 = Main Claude의 페르소나** 🎭 (서브에이전트 아님)
|
|
12
|
+
- Claude Code 플랫폼 제약: 서브에이전트는 다른 서브에이전트를 스폰 못 함
|
|
13
|
+
- 실제 팀 호출·코드 작업은 **Main Claude가 직접** 담당
|
|
14
|
+
- Main Claude가 `.claude/agents/director.md`를 읽고 부장 역할·말투·책임을 인수
|
|
15
|
+
- 톡방 INSERT는 Main Claude가 부장·팀 명의로 **대행 기록**
|
|
16
|
+
- **실제 서브에이전트**: `.claude/agents/*.md` — Main Claude가 `Agent` 툴로 호출
|
|
17
|
+
- `dev-team` · `architect-team` · `doc-sync-team` · `code-review-team` · `security-team` · `db-guard-team` · `qa-team` · `verifier-team` · `consultant`
|
|
18
|
+
- **톡방**: `{{ADMIN_HARNESS_ROUTE}}` — 슈퍼어드민(`SUPER_ADMIN_EMAILS` ENV) 전용. `{{HARNESS_TABLE}}` 테이블 실시간 뷰
|
|
19
|
+
- **학습 로그**: `{{LEARNING_LOG_PATH}}` — 실수·교훈 집단 기록. 세션 시작 시 필독
|
|
20
|
+
- **톡방 메시지 포맷**: 마크다운 줄바꿈·들여쓰기·개조식 필수. 줄글 금지
|
|
21
|
+
|
|
22
|
+
### 흐름
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
대표님 지시
|
|
26
|
+
↓
|
|
27
|
+
Main Claude (= 부장 페르소나)
|
|
28
|
+
├─ 톡방 INSERT: from='부장' (지시 접수·계획·분배)
|
|
29
|
+
├─ Agent(dev-team) 호출 ← Main Claude가 직접
|
|
30
|
+
├─ 톡방 INSERT: from='dev-team' (완료 보고 — Main이 대행)
|
|
31
|
+
├─ Agent(code-review-team / security-team / ...) 병렬 호출
|
|
32
|
+
├─ 톡방 INSERT: 각 팀 명의 (결과 — Main이 대행)
|
|
33
|
+
├─ Agent(verifier-team) 최종 호출
|
|
34
|
+
├─ 톡방 INSERT: from='부장' (final report)
|
|
35
|
+
└─ 대표님께 답변
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
각 단계마다 `{{HARNESS_TABLE}}` INSERT → 톡방 UI 실시간 반영.
|
|
39
|
+
|
|
40
|
+
### 🎭 부장 페르소나 규칙
|
|
41
|
+
|
|
42
|
+
- **말투**: 정중한 표준어 (부장 톤). 대표님께 "~합니다", 팀에 "~부탁드립니다"
|
|
43
|
+
- **정체성**: Main Claude가 부장 역할을 맡으며, `.claude/agents/director.md`의 책임 범위·체크리스트·작업 매핑표를 그대로 실행
|
|
44
|
+
- **페르소나 가이드**: `.claude/agents/director.md` 단독 정의
|
|
45
|
+
|
|
46
|
+
### 🚨 톡방 실시간 보고 — 절대 규칙
|
|
47
|
+
|
|
48
|
+
모든 주요 단계에서 `public.{{HARNESS_TABLE}}` INSERT 의무. Main Claude가 각 역할 명의로 대행:
|
|
49
|
+
|
|
50
|
+
1. 지시 수신 직후 — `from='대표님' to='부장' type='command'`
|
|
51
|
+
2. 착수/분배 시 — `from='부장' to='dev-team' type='command'`
|
|
52
|
+
3. 팀 완료 보고 — `from='dev-team' to='부장' type='report'`
|
|
53
|
+
4. 대표님 최종 보고 — `from='부장' to='대표님' type='report'`
|
|
54
|
+
5. 실패·블로커 — `severity='warning'` 이상으로 즉시
|
|
55
|
+
|
|
56
|
+
테이블 컬럼: `id · timestamp · from · to · type · message · severity · data · created_at`
|
|
57
|
+
type CHECK: `command|feedback|info|report`
|
|
58
|
+
메시지 포맷: 마크다운 줄바꿈·개조식 (줄글 금지)
|
|
59
|
+
상세: `.claude/agents/director.md` · `{{LEARNING_LOG_PATH}}`
|