@damian87/omp 0.1.0 → 0.3.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/.github/copilot-instructions.md +15 -0
- package/.github/skills/caveman/SKILL.md +22 -11
- package/.github/skills/code-review/SKILL.md +27 -13
- package/.github/skills/debug/SKILL.md +29 -13
- package/.github/skills/grill-me/SKILL.md +19 -7
- package/.github/skills/jira-ticket/SKILL.md +34 -12
- package/.github/skills/omp-autopilot/SKILL.md +67 -12
- package/.github/skills/prototype/SKILL.md +30 -12
- package/.github/skills/ralph/SKILL.md +62 -13
- package/.github/skills/ralplan/SKILL.md +21 -12
- package/.github/skills/research-codebase/SKILL.md +58 -0
- package/.github/skills/research-codebase/reference/agent-prompts.md +38 -0
- package/.github/skills/research-codebase/reference/follow-up.md +21 -0
- package/.github/skills/research-codebase/reference/permalink.md +26 -0
- package/.github/skills/research-codebase/reference/template.md +75 -0
- package/.github/skills/tdd/SKILL.md +27 -10
- package/.github/skills/team/SKILL.md +95 -13
- package/.github/skills/team/scripts/team-launch.sh +132 -0
- package/.github/skills/ultraqa/SKILL.md +55 -11
- package/.github/skills/ultrawork/SKILL.md +66 -13
- package/.github/skills/verify/SKILL.md +29 -11
- package/.github/skills/worktree/SKILL.md +51 -0
- package/README.md +28 -14
- package/catalog/capabilities.json +104 -12
- package/catalog/skills-general.json +62 -10
- package/dist/src/cli.js +30 -3
- package/dist/src/cli.js.map +1 -1
- package/dist/src/copilot/launch.js +42 -2
- package/dist/src/copilot/launch.js.map +1 -1
- package/dist/src/mode-state/paths.d.ts +6 -0
- package/dist/src/mode-state/paths.js +12 -0
- package/dist/src/mode-state/paths.js.map +1 -1
- package/dist/src/team/api.d.ts +60 -1
- package/dist/src/team/api.js +101 -0
- package/dist/src/team/api.js.map +1 -1
- package/dist/src/team/config.d.ts +7 -0
- package/dist/src/team/config.js +16 -0
- package/dist/src/team/config.js.map +1 -0
- package/dist/src/team/mailbox.d.ts +30 -0
- package/dist/src/team/mailbox.js +188 -0
- package/dist/src/team/mailbox.js.map +1 -0
- package/dist/src/team/runtime.d.ts +9 -1
- package/dist/src/team/runtime.js +18 -12
- package/dist/src/team/runtime.js.map +1 -1
- package/dist/src/team/types.d.ts +25 -0
- package/dist/src/team/worker-bootstrap.js +29 -0
- package/dist/src/team/worker-bootstrap.js.map +1 -1
- package/dist/test/catalog.test.d.ts +1 -0
- package/dist/test/catalog.test.js +21 -0
- package/dist/test/catalog.test.js.map +1 -0
- package/dist/test/jira.test.d.ts +1 -0
- package/dist/test/jira.test.js +26 -0
- package/dist/test/jira.test.js.map +1 -0
- package/dist/test/lint.test.d.ts +1 -0
- package/dist/test/lint.test.js +9 -0
- package/dist/test/lint.test.js.map +1 -0
- package/dist/test/sync.test.d.ts +1 -0
- package/dist/test/sync.test.js +15 -0
- package/dist/test/sync.test.js.map +1 -0
- package/docs/general-skills.md +5 -4
- package/docs/plans/2026-05-29-team-messaging-and-nudge-gating-design.md +170 -0
- package/docs/self-evolve.md +2 -2
- package/package.json +1 -1
- package/scripts/lib/version-check.mjs +82 -0
- package/scripts/session-start.mjs +8 -2
- package/.github/skills/codebase-research/SKILL.md +0 -20
package/dist/src/team/types.d.ts
CHANGED
|
@@ -43,3 +43,28 @@ export interface OutboxMessage {
|
|
|
43
43
|
detail?: string;
|
|
44
44
|
timestamp: string;
|
|
45
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* A team mailbox message (worker<->worker or worker<->leader).
|
|
48
|
+
* Mailbox files are append-only JSONL; delivery is tracked via separate
|
|
49
|
+
* append-only DeliveryReceipt lines, never by rewriting the message line.
|
|
50
|
+
*/
|
|
51
|
+
export interface MailboxMessage {
|
|
52
|
+
type: "message";
|
|
53
|
+
id: string;
|
|
54
|
+
from: string;
|
|
55
|
+
to: string;
|
|
56
|
+
body: string;
|
|
57
|
+
timestamp: string;
|
|
58
|
+
}
|
|
59
|
+
/** Append-only delivery acknowledgement for a MailboxMessage. */
|
|
60
|
+
export interface DeliveryReceipt {
|
|
61
|
+
type: "delivery-receipt";
|
|
62
|
+
messageId: string;
|
|
63
|
+
deliveredAt: string;
|
|
64
|
+
}
|
|
65
|
+
/** A single line in a mailbox JSONL file. */
|
|
66
|
+
export type MailboxLine = MailboxMessage | DeliveryReceipt;
|
|
67
|
+
/** Merged view returned by listMailbox: a message with optional delivery info. */
|
|
68
|
+
export interface MailboxMessageView extends MailboxMessage {
|
|
69
|
+
deliveredAt?: string;
|
|
70
|
+
}
|
|
@@ -24,6 +24,22 @@ export function buildInboxMarkdown(opts) {
|
|
|
24
24
|
to: "failed",
|
|
25
25
|
claim_token: "<claim_token>",
|
|
26
26
|
});
|
|
27
|
+
const sendToLeaderInput = JSON.stringify({
|
|
28
|
+
team_name: opts.teamName,
|
|
29
|
+
from: opts.workerName,
|
|
30
|
+
to: "leader",
|
|
31
|
+
body: "<message>",
|
|
32
|
+
});
|
|
33
|
+
const sendToPeerInput = JSON.stringify({
|
|
34
|
+
team_name: opts.teamName,
|
|
35
|
+
from: opts.workerName,
|
|
36
|
+
to: "<teammate>",
|
|
37
|
+
body: "<message>",
|
|
38
|
+
});
|
|
39
|
+
const mailboxListInput = JSON.stringify({
|
|
40
|
+
team_name: opts.teamName,
|
|
41
|
+
worker: opts.workerName,
|
|
42
|
+
});
|
|
27
43
|
return [
|
|
28
44
|
"## REQUIRED: Task Lifecycle Commands",
|
|
29
45
|
"You MUST run these commands. Do NOT skip any step.",
|
|
@@ -47,6 +63,19 @@ export function buildInboxMarkdown(opts) {
|
|
|
47
63
|
"## Description",
|
|
48
64
|
opts.task.description,
|
|
49
65
|
"",
|
|
66
|
+
"## Messaging",
|
|
67
|
+
"You can message the leader OR any teammate directly. The recipient is auto-nudged.",
|
|
68
|
+
"Unknown recipient names are rejected with `unknown_recipient`.",
|
|
69
|
+
"",
|
|
70
|
+
"Message the leader:",
|
|
71
|
+
` omp team api send-message --input '${sendToLeaderInput}' --json`,
|
|
72
|
+
"",
|
|
73
|
+
"Message a teammate (replace <teammate> with their worker name):",
|
|
74
|
+
` omp team api send-message --input '${sendToPeerInput}' --json`,
|
|
75
|
+
"",
|
|
76
|
+
"Check your mailbox:",
|
|
77
|
+
` omp team api mailbox-list --input '${mailboxListInput}' --json`,
|
|
78
|
+
"",
|
|
50
79
|
].join("\n");
|
|
51
80
|
}
|
|
52
81
|
//# sourceMappingURL=worker-bootstrap.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker-bootstrap.js","sourceRoot":"","sources":["../../../src/team/worker-bootstrap.ts"],"names":[],"mappings":"AASA,MAAM,UAAU,kBAAkB,CAAC,IAAkB;IACnD,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC;QAChC,SAAS,EAAE,IAAI,CAAC,QAAQ;QACxB,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE;QACrB,MAAM,EAAE,IAAI,CAAC,UAAU;QACvB,GAAG,EAAE,IAAI,CAAC,GAAG;KACd,CAAC,CAAC;IACH,MAAM,qBAAqB,GAAG,IAAI,CAAC,SAAS,CAAC;QAC3C,SAAS,EAAE,IAAI,CAAC,QAAQ;QACxB,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE;QACrB,MAAM,EAAE,IAAI,CAAC,UAAU;QACvB,GAAG,EAAE,IAAI,CAAC,GAAG;QACb,IAAI,EAAE,aAAa;QACnB,EAAE,EAAE,WAAW;QACf,WAAW,EAAE,eAAe;QAC5B,MAAM,EAAE,cAAc;KACvB,CAAC,CAAC;IACH,MAAM,iBAAiB,GAAG,IAAI,CAAC,SAAS,CAAC;QACvC,SAAS,EAAE,IAAI,CAAC,QAAQ;QACxB,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE;QACrB,MAAM,EAAE,IAAI,CAAC,UAAU;QACvB,GAAG,EAAE,IAAI,CAAC,GAAG;QACb,IAAI,EAAE,aAAa;QACnB,EAAE,EAAE,QAAQ;QACZ,WAAW,EAAE,eAAe;KAC7B,CAAC,CAAC;IACH,OAAO;QACL,sCAAsC;QACtC,oDAAoD;QACpD,EAAE;QACF,qBAAqB;QACrB,uCAAuC,UAAU,UAAU;QAC3D,4CAA4C;QAC5C,EAAE;QACF,iCAAiC;QACjC,EAAE;QACF,iDAAiD;QACjD,mDAAmD,qBAAqB,UAAU;QAClF,EAAE;QACF,gBAAgB;QAChB,mDAAmD,iBAAiB,UAAU;QAC9E,EAAE;QACF,oBAAoB;QACpB,YAAY,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE;QAC1B,WAAW,IAAI,CAAC,UAAU,EAAE;QAC5B,EAAE;QACF,gBAAgB;QAChB,IAAI,CAAC,IAAI,CAAC,WAAW;QACrB,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC"}
|
|
1
|
+
{"version":3,"file":"worker-bootstrap.js","sourceRoot":"","sources":["../../../src/team/worker-bootstrap.ts"],"names":[],"mappings":"AASA,MAAM,UAAU,kBAAkB,CAAC,IAAkB;IACnD,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC;QAChC,SAAS,EAAE,IAAI,CAAC,QAAQ;QACxB,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE;QACrB,MAAM,EAAE,IAAI,CAAC,UAAU;QACvB,GAAG,EAAE,IAAI,CAAC,GAAG;KACd,CAAC,CAAC;IACH,MAAM,qBAAqB,GAAG,IAAI,CAAC,SAAS,CAAC;QAC3C,SAAS,EAAE,IAAI,CAAC,QAAQ;QACxB,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE;QACrB,MAAM,EAAE,IAAI,CAAC,UAAU;QACvB,GAAG,EAAE,IAAI,CAAC,GAAG;QACb,IAAI,EAAE,aAAa;QACnB,EAAE,EAAE,WAAW;QACf,WAAW,EAAE,eAAe;QAC5B,MAAM,EAAE,cAAc;KACvB,CAAC,CAAC;IACH,MAAM,iBAAiB,GAAG,IAAI,CAAC,SAAS,CAAC;QACvC,SAAS,EAAE,IAAI,CAAC,QAAQ;QACxB,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE;QACrB,MAAM,EAAE,IAAI,CAAC,UAAU;QACvB,GAAG,EAAE,IAAI,CAAC,GAAG;QACb,IAAI,EAAE,aAAa;QACnB,EAAE,EAAE,QAAQ;QACZ,WAAW,EAAE,eAAe;KAC7B,CAAC,CAAC;IACH,MAAM,iBAAiB,GAAG,IAAI,CAAC,SAAS,CAAC;QACvC,SAAS,EAAE,IAAI,CAAC,QAAQ;QACxB,IAAI,EAAE,IAAI,CAAC,UAAU;QACrB,EAAE,EAAE,QAAQ;QACZ,IAAI,EAAE,WAAW;KAClB,CAAC,CAAC;IACH,MAAM,eAAe,GAAG,IAAI,CAAC,SAAS,CAAC;QACrC,SAAS,EAAE,IAAI,CAAC,QAAQ;QACxB,IAAI,EAAE,IAAI,CAAC,UAAU;QACrB,EAAE,EAAE,YAAY;QAChB,IAAI,EAAE,WAAW;KAClB,CAAC,CAAC;IACH,MAAM,gBAAgB,GAAG,IAAI,CAAC,SAAS,CAAC;QACtC,SAAS,EAAE,IAAI,CAAC,QAAQ;QACxB,MAAM,EAAE,IAAI,CAAC,UAAU;KACxB,CAAC,CAAC;IACH,OAAO;QACL,sCAAsC;QACtC,oDAAoD;QACpD,EAAE;QACF,qBAAqB;QACrB,uCAAuC,UAAU,UAAU;QAC3D,4CAA4C;QAC5C,EAAE;QACF,iCAAiC;QACjC,EAAE;QACF,iDAAiD;QACjD,mDAAmD,qBAAqB,UAAU;QAClF,EAAE;QACF,gBAAgB;QAChB,mDAAmD,iBAAiB,UAAU;QAC9E,EAAE;QACF,oBAAoB;QACpB,YAAY,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE;QAC1B,WAAW,IAAI,CAAC,UAAU,EAAE;QAC5B,EAAE;QACF,gBAAgB;QAChB,IAAI,CAAC,IAAI,CAAC,WAAW;QACrB,EAAE;QACF,cAAc;QACd,oFAAoF;QACpF,gEAAgE;QAChE,EAAE;QACF,qBAAqB;QACrB,yCAAyC,iBAAiB,UAAU;QACpE,EAAE;QACF,iEAAiE;QACjE,yCAAyC,eAAe,UAAU;QAClE,EAAE;QACF,qBAAqB;QACrB,yCAAyC,gBAAgB,UAAU;QACnE,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
import { loadCapabilityCatalog, loadSkillCatalog, validateCatalog } from '../src/catalog.js';
|
|
4
|
+
test('catalog contains approved MVP skills and valid projections', () => {
|
|
5
|
+
const catalog = loadSkillCatalog();
|
|
6
|
+
assert.deepEqual(validateCatalog(), []);
|
|
7
|
+
const names = catalog.skills.map((skill) => skill.name);
|
|
8
|
+
assert.deepEqual(names, ['grill', 'verify', 'jira-ticket', 'code-review', 'qa']);
|
|
9
|
+
assert.ok(catalog.projectionCommands.includes('grill-me'));
|
|
10
|
+
assert.ok(catalog.projectionCommands.includes('team'));
|
|
11
|
+
assert.ok(catalog.projectionCommands.includes('ralph'));
|
|
12
|
+
});
|
|
13
|
+
test('team and ralph stay thin handoff capabilities for Copilot', () => {
|
|
14
|
+
const capabilities = loadCapabilityCatalog();
|
|
15
|
+
for (const name of ['team', 'ralph']) {
|
|
16
|
+
const capability = capabilities.capabilities.find((entry) => entry.name === name);
|
|
17
|
+
assert.ok(capability, `${name} capability exists`);
|
|
18
|
+
assert.equal(capability.support.copilot, 'thin-handoff');
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
//# sourceMappingURL=catalog.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog.test.js","sourceRoot":"","sources":["../../test/catalog.test.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAE7F,IAAI,CAAC,4DAA4D,EAAE,GAAG,EAAE;IACtE,MAAM,OAAO,GAAG,gBAAgB,EAAE,CAAC;IACnC,MAAM,CAAC,SAAS,CAAC,eAAe,EAAE,EAAE,EAAE,CAAC,CAAC;IAExC,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACxD,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,aAAa,EAAE,IAAI,CAAC,CAAC,CAAC;IACjF,MAAM,CAAC,EAAE,CAAC,OAAO,CAAC,kBAAkB,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC;IAC3D,MAAM,CAAC,EAAE,CAAC,OAAO,CAAC,kBAAkB,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;IACvD,MAAM,CAAC,EAAE,CAAC,OAAO,CAAC,kBAAkB,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;AAC1D,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,2DAA2D,EAAE,GAAG,EAAE;IACrE,MAAM,YAAY,GAAG,qBAAqB,EAAE,CAAC;IAC7C,KAAK,MAAM,IAAI,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;QACrC,MAAM,UAAU,GAAG,YAAY,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;QAClF,MAAM,CAAC,EAAE,CAAC,UAAU,EAAE,GAAG,IAAI,oBAAoB,CAAC,CAAC;QACnD,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;IAC3D,CAAC;AACH,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
import { commentPayload, createIssuePayload, discoverJiraConfig, isJiraConfigured, linkFallbackPayload, safeUpdatePayload, transitionFallbackPayload } from '../src/jira.js';
|
|
4
|
+
test('jira config discovery supports env and configuration detection', () => {
|
|
5
|
+
const config = discoverJiraConfig('/tmp/no-such-root', {
|
|
6
|
+
JIRA_SITE_URL: 'https://example.atlassian.net',
|
|
7
|
+
JIRA_EMAIL: 'agent@example.com',
|
|
8
|
+
JIRA_API_TOKEN: 'secret-token',
|
|
9
|
+
JIRA_PROJECT_KEY: 'OMC'
|
|
10
|
+
});
|
|
11
|
+
assert.equal(config.siteUrl, 'https://example.atlassian.net');
|
|
12
|
+
assert.equal(config.projectKey, 'OMC');
|
|
13
|
+
assert.equal(isJiraConfigured(config), true);
|
|
14
|
+
});
|
|
15
|
+
test('jira adapter renders create, comment, update, and fallback payloads', () => {
|
|
16
|
+
const config = discoverJiraConfig('/tmp/no-such-root', { JIRA_PROJECT_KEY: 'OMC' });
|
|
17
|
+
const create = createIssuePayload(config, { summary: 'Implement slice', description: 'Body' });
|
|
18
|
+
assert.equal(create.operation, 'create');
|
|
19
|
+
assert.equal(create.configured, false);
|
|
20
|
+
assert.match(JSON.stringify(create.body), /Implement slice/);
|
|
21
|
+
assert.equal(commentPayload(config, 'OMC-1', 'Evidence').operation, 'comment');
|
|
22
|
+
assert.equal(safeUpdatePayload(config, 'OMC-1', { summary: 'New' }).method, 'PUT');
|
|
23
|
+
assert.equal(transitionFallbackPayload(config, 'OMC-1', 'Done').configured, false);
|
|
24
|
+
assert.equal(linkFallbackPayload(config, 'OMC-1', 'OMC-2').operation, 'link-fallback');
|
|
25
|
+
});
|
|
26
|
+
//# sourceMappingURL=jira.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jira.test.js","sourceRoot":"","sources":["../../test/jira.test.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAC;AAE7K,IAAI,CAAC,gEAAgE,EAAE,GAAG,EAAE;IAC1E,MAAM,MAAM,GAAG,kBAAkB,CAAC,mBAAmB,EAAE;QACrD,aAAa,EAAE,+BAA+B;QAC9C,UAAU,EAAE,mBAAmB;QAC/B,cAAc,EAAE,cAAc;QAC9B,gBAAgB,EAAE,KAAK;KACxB,CAAC,CAAC;IAEH,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,EAAE,+BAA+B,CAAC,CAAC;IAC9D,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IACvC,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC;AAC/C,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,qEAAqE,EAAE,GAAG,EAAE;IAC/E,MAAM,MAAM,GAAG,kBAAkB,CAAC,mBAAmB,EAAE,EAAE,gBAAgB,EAAE,KAAK,EAAE,CAAC,CAAC;IAEpF,MAAM,MAAM,GAAG,kBAAkB,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC,CAAC;IAC/F,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IACzC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IACvC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,iBAAiB,CAAC,CAAC;IAE7D,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;IAC/E,MAAM,CAAC,KAAK,CAAC,iBAAiB,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACnF,MAAM,CAAC,KAAK,CAAC,yBAAyB,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IACnF,MAAM,CAAC,KAAK,CAAC,mBAAmB,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,SAAS,EAAE,eAAe,CAAC,CAAC;AACzF,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
import { lintSkills } from '../src/lint.js';
|
|
4
|
+
import { workspaceRoot } from '../src/project.js';
|
|
5
|
+
test('workspace skills satisfy Phase 1 catalog lint', () => {
|
|
6
|
+
const issues = lintSkills(workspaceRoot('..'));
|
|
7
|
+
assert.deepEqual(issues.filter((issue) => issue.level === 'error'), []);
|
|
8
|
+
});
|
|
9
|
+
//# sourceMappingURL=lint.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lint.test.js","sourceRoot":"","sources":["../../test/lint.test.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAElD,IAAI,CAAC,+CAA+C,EAAE,GAAG,EAAE;IACzD,MAAM,MAAM,GAAG,UAAU,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC;IAC/C,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,KAAK,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC;AAC1E,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
import { projectCopilotCommands } from '../src/sync.js';
|
|
4
|
+
test('dry-run projects skill aliases and thin runtime handoffs', () => {
|
|
5
|
+
const files = projectCopilotCommands();
|
|
6
|
+
const paths = files.map((file) => file.path);
|
|
7
|
+
assert.ok(paths.includes('.github/copilot/commands/grill.md'));
|
|
8
|
+
assert.ok(paths.includes('.github/copilot/commands/grill-me.md'));
|
|
9
|
+
assert.ok(paths.includes('.github/copilot/commands/team.md'));
|
|
10
|
+
assert.ok(paths.includes('.github/copilot/commands/ralph.md'));
|
|
11
|
+
const team = files.find((file) => file.path.endsWith('/team.md'));
|
|
12
|
+
assert.ok(team?.content.includes('thin capability handoff'));
|
|
13
|
+
assert.ok(team?.content.includes('not a Copilot-native durable runtime'));
|
|
14
|
+
});
|
|
15
|
+
//# sourceMappingURL=sync.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync.test.js","sourceRoot":"","sources":["../../test/sync.test.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAExD,IAAI,CAAC,0DAA0D,EAAE,GAAG,EAAE;IACpE,MAAM,KAAK,GAAG,sBAAsB,EAAE,CAAC;IACvC,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAE7C,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,mCAAmC,CAAC,CAAC,CAAC;IAC/D,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,sCAAsC,CAAC,CAAC,CAAC;IAClE,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,kCAAkC,CAAC,CAAC,CAAC;IAC9D,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,mCAAmC,CAAC,CAAC,CAAC;IAE/D,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC;IAClE,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,QAAQ,CAAC,yBAAyB,CAAC,CAAC,CAAC;IAC7D,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,QAAQ,CAAC,sCAAsC,CAAC,CAAC,CAAC;AAC5E,CAAC,CAAC,CAAC"}
|
package/docs/general-skills.md
CHANGED
|
@@ -6,14 +6,14 @@ The canonical skill source is the repo-local `.github/skills` directory. This is
|
|
|
6
6
|
|
|
7
7
|
| Skill | Capability IDs | Purpose |
|
|
8
8
|
| --- | --- | --- |
|
|
9
|
-
| `/codebase
|
|
9
|
+
| `/research-codebase` | `research-codebase`, `research.codebase` | Comprehensive codebase research with tiered effort. |
|
|
10
10
|
| `/grill-me` | `grill-me`, `planning.challenge` | Ask one sharp clarification question when ambiguity remains. |
|
|
11
11
|
| `/ralplan` | `ralplan`, `planning.consensus` | Produce implementation-ready plan, tests, and risks. |
|
|
12
|
-
| `/team` | `team`, `execution.parallel` | Split approved work into parallel
|
|
12
|
+
| `/team` | `team`, `execution.parallel` | Split approved work into parallel tmux panes running interactive agents. |
|
|
13
13
|
| `/ralph` | `ralph`, `execution.single-owner` | Single-owner execute-fix-verify loop. |
|
|
14
14
|
| `/ultrawork` | `ultrawork`, `execution.parallel` | Batch many independent small tasks. |
|
|
15
15
|
| `/ultraqa` | `ultraqa`, `qa.behavioral` | Adversarial behavior and regression QA. |
|
|
16
|
-
| `/
|
|
16
|
+
| `/omp-autopilot` | `omp-autopilot`, `execution.autonomous` | Lightweight end-to-end flow across the other skills. (Renamed from `/autopilot` to avoid Copilot CLI built-in collision.) |
|
|
17
17
|
| `/code-review` | `code-review`, `review.independent` | Review completed changes before merge or handoff. |
|
|
18
18
|
| `/verify` | `verify`, `verification.evidence` | Prove completion claims with evidence. |
|
|
19
19
|
| `/jira-ticket` | `jira-ticket`, `tracker.ticket` | Render Jira create/comment/safe-update payloads. |
|
|
@@ -21,6 +21,7 @@ The canonical skill source is the repo-local `.github/skills` directory. This is
|
|
|
21
21
|
| `/caveman` | `caveman`, `communication.compact` | Ultra-compact response mode. |
|
|
22
22
|
| `/debug` | `debug`, `debug.systematic` | Reproduce, diagnose, fix, and regression-test bugs. |
|
|
23
23
|
| `/tdd` | `tdd`, `testing.tdd` | Red-green-refactor for behavior changes. |
|
|
24
|
+
| `/worktree` | `worktree`, `workflow.worktree` | Git worktree-based parallel branch work. |
|
|
24
25
|
|
|
25
26
|
## Repo-local layout
|
|
26
27
|
|
|
@@ -52,7 +53,7 @@ It does not imply a durable execution runtime. Phase 1 execution skills such as
|
|
|
52
53
|
## Phase 1 flow
|
|
53
54
|
|
|
54
55
|
```text
|
|
55
|
-
/codebase
|
|
56
|
+
/research-codebase
|
|
56
57
|
-> /grill-me when unclear or risky
|
|
57
58
|
-> /ralplan
|
|
58
59
|
-> /team if lanes are independent, otherwise /ralph or /ultrawork
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# Design: Team messaging (worker↔worker / worker↔leader) + nudge gating
|
|
2
|
+
|
|
3
|
+
> Status: **Design only — no implementation yet.** Target repo: `oh-my-copilot`.
|
|
4
|
+
> Date: 2026-05-29
|
|
5
|
+
|
|
6
|
+
## 1. Problem & evidence
|
|
7
|
+
|
|
8
|
+
Two related gaps in `src/team`:
|
|
9
|
+
|
|
10
|
+
**A. Workers cannot message anyone.** The entire `team api` surface (`src/cli.ts:35`) is:
|
|
11
|
+
|
|
12
|
+
- `team api claim-task`
|
|
13
|
+
- `team api transition-task-status`
|
|
14
|
+
|
|
15
|
+
There is **no `send-message`, `broadcast`, or mailbox** anywhere in `src`. The only
|
|
16
|
+
worker→lead signal is an outbox line auto-appended on task transition
|
|
17
|
+
(`src/team/api.ts:55-65`), consumed by the monitor (`readNewOutbox`). So a worker
|
|
18
|
+
cannot ask the lead a question, hand off to a peer, or nudge a teammate. This is the
|
|
19
|
+
"teams says limitation can't message" behavior.
|
|
20
|
+
|
|
21
|
+
This must work **without MCP** (Copilot CLI; MCP is disabled in the target org). The
|
|
22
|
+
existing design is already file + CLI based, so this is the natural fit.
|
|
23
|
+
|
|
24
|
+
**B. Nudge is ungated.** `NudgeTracker` runs only inside `monitorTeam`
|
|
25
|
+
(`src/team/runtime.ts:224`) and is **default-on** (`:213`). `monitorTeam` is not yet
|
|
26
|
+
wired into any command, so nudge effectively never fires today — but when it is wired
|
|
27
|
+
in, default-on would nudge every monitored team run (including read-only polling).
|
|
28
|
+
Desired: **off by default, but ON for `/team` orchestration runs and for active
|
|
29
|
+
ralph/loop modes.** Plain read-only polling (`team status`) and library/one-shot use
|
|
30
|
+
stay quiet.
|
|
31
|
+
|
|
32
|
+
## 2. What already exists (reuse, don't rebuild)
|
|
33
|
+
|
|
34
|
+
- `state-paths.ts` already defines `mailboxDir` (`.omp/state/team/<team>/mailbox`) and
|
|
35
|
+
`dispatchDir`, and `ensureTeamDirs` already creates them. Workers already have
|
|
36
|
+
`inboxFile` (`workers/<name>/inbox.md`), `outboxFile`, `heartbeatFile`, pane id in
|
|
37
|
+
config (`Worker.paneId`).
|
|
38
|
+
- `tmux.ts` exposes `sendToWorker(api, paneId, text, …)` — the nudge transport.
|
|
39
|
+
- `outbox.ts` has a robust byte-cursor JSONL reader we can mirror for mailbox reads.
|
|
40
|
+
|
|
41
|
+
## 3. Feature A — messaging
|
|
42
|
+
|
|
43
|
+
### 3.1 Data model
|
|
44
|
+
|
|
45
|
+
New file `src/team/mailbox.ts`. One JSONL file per recipient:
|
|
46
|
+
`.omp/state/team/<team>/mailbox/<recipient>.jsonl`, plus a `.<recipient>.offset` cursor
|
|
47
|
+
(mirror `outbox.ts`). `leader` is the reserved recipient name for the lead.
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
export interface MailboxMessage {
|
|
51
|
+
id: string; // uuid
|
|
52
|
+
from: string; // worker name or "leader"
|
|
53
|
+
to: string; // worker name or "leader"
|
|
54
|
+
body: string;
|
|
55
|
+
timestamp: string; // ISO
|
|
56
|
+
deliveredAt?: string;
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Functions: `appendMailbox`, `readNewMailbox`/`peekMailbox` (cursor-based),
|
|
61
|
+
`listMailbox`, `markDelivered`.
|
|
62
|
+
|
|
63
|
+
### 3.2 API surface (additions to `src/team/api.ts` + `cli.ts`)
|
|
64
|
+
|
|
65
|
+
- `team api send-message --input '{team_name, from, to, body, cwd?}'`
|
|
66
|
+
→ validates `to` is `leader` or a registered worker (`config.workers`), rejects
|
|
67
|
+
unknown recipients with `unknown_recipient` (no phantom mailbox), appends to the
|
|
68
|
+
recipient mailbox, then **nudges the recipient pane** (see 3.3).
|
|
69
|
+
- `team api broadcast --input '{team_name, from, body, cwd?}'`
|
|
70
|
+
→ fans out to **every worker except `from`, PLUS the `leader` mailbox**.
|
|
71
|
+
Reuses oh-my-claudecode's mechanism (`teamBroadcast`, `team-ops.ts:604`: loop
|
|
72
|
+
recipients → `send` to each), but oh-my-claudecode skips the lead (it only iterates
|
|
73
|
+
`cfg.workers`); here we additionally append `leader` to the recipient set so the lead
|
|
74
|
+
receives broadcasts too. Sender is always excluded (no self-message, no echo loop).
|
|
75
|
+
- `team api mailbox-list --input '{team_name, worker, cwd?}'` → list (optionally
|
|
76
|
+
undelivered only).
|
|
77
|
+
- `team api mailbox-mark-delivered --input '{team_name, worker, message_id, cwd?}'`.
|
|
78
|
+
|
|
79
|
+
All pure file ops, callable directly in tests (no MCP, no tmux required for persistence).
|
|
80
|
+
|
|
81
|
+
### 3.3 Delivery / nudge
|
|
82
|
+
|
|
83
|
+
After persisting, look up the recipient's `paneId` from `config.workers` (or
|
|
84
|
+
`leader_pane_id` in config). If present and a tmux session is live, call
|
|
85
|
+
`sendToWorker(...)` with a short trigger. If no pane (worker not spawned / headless
|
|
86
|
+
test), persistence still succeeds — delivery is pull-based via `mailbox-list`.
|
|
87
|
+
**Nudge-on-send reuses the same `sendToWorker` transport, independent of the
|
|
88
|
+
idle-`NudgeTracker`.**
|
|
89
|
+
|
|
90
|
+
**Throttle/coalesce — reuse what already exists, build nothing new:**
|
|
91
|
+
- The idle `NudgeTracker` (`idle-nudge.ts`) already carries the same throttle as
|
|
92
|
+
oh-my-claudecode (`scanIntervalMs: 5000`, `maxCount: 3`) — unchanged.
|
|
93
|
+
- For send-nudge, mirror oh-my-claudecode's `generateMailboxTriggerMessage(count)`:
|
|
94
|
+
the trigger states the **unread count** (`listMailbox(...).filter(!deliveredAt).length`)
|
|
95
|
+
— "N new msg(s): read your mailbox …". Multiple messages arriving before the worker
|
|
96
|
+
reads simply raise the count in the next poke rather than spamming distinct triggers.
|
|
97
|
+
No separate coalescing/debounce machinery.
|
|
98
|
+
|
|
99
|
+
### 3.4 Worker overlay (`worker-bootstrap.ts buildInboxMarkdown`)
|
|
100
|
+
|
|
101
|
+
Add a "Messaging" section teaching workers they may message the **lead OR any teammate**:
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
omp team api send-message --input '{"team_name":…,"from":"<me>","to":"leader","body":"…"}' --json
|
|
105
|
+
omp team api send-message --input '{"team_name":…,"from":"<me>","to":"<teammate>","body":"…"}' --json
|
|
106
|
+
omp team api mailbox-list --input '{"team_name":…,"worker":"<me>"}' --json
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
State that the recipient is auto-nudged, and that unknown names return `unknown_recipient`.
|
|
110
|
+
|
|
111
|
+
### 3.5 Tests (Vitest, file-based, no MCP)
|
|
112
|
+
|
|
113
|
+
`test/team/mailbox.test.ts` + `test/team/api.messaging.test.ts`:
|
|
114
|
+
worker→leader, worker→peer, broadcast-excludes-sender, mailbox-list/mark-delivered,
|
|
115
|
+
unknown-recipient rejection, cursor advance. A nudge unit test mocks `tmux`/`sendToWorker`
|
|
116
|
+
to assert the recipient pane is poked.
|
|
117
|
+
|
|
118
|
+
## 4. Feature B — nudge gating
|
|
119
|
+
|
|
120
|
+
Change `monitorTeam` so nudge is **off by default**, but **ON for `/team`
|
|
121
|
+
orchestration runs and active ralph/loop modes**:
|
|
122
|
+
|
|
123
|
+
- Default `opts.nudge?.enabled` to **`false`** (flip current `!== false`).
|
|
124
|
+
- Add `resolveNudgeEnabled(opts, cwd)` → `true` when ANY of:
|
|
125
|
+
1. `opts.nudge?.enabled === true` — set by the **`/team` command's monitor loop**
|
|
126
|
+
(the orchestration spawn+monitor path in `cli.ts` passes `nudge:{enabled:true}`).
|
|
127
|
+
This is what makes nudge ON for `/team`.
|
|
128
|
+
2. A loop-mode state file is active: `readRalph(cwd)?.active`, or the equivalent
|
|
129
|
+
`ultrawork`/`ultraqa` state (modes live at `.omp/state/<mode>.json`,
|
|
130
|
+
`src/mode-state/paths.ts`).
|
|
131
|
+
3. Falls back to `false` otherwise.
|
|
132
|
+
- Add a small `isLoopModeActive(cwd)` helper in `mode-state` (reads the three mode
|
|
133
|
+
files) so condition 2 is one source of truth.
|
|
134
|
+
|
|
135
|
+
Result:
|
|
136
|
+
- **`/team` orchestration** → nudge ON (condition 1).
|
|
137
|
+
- **ralph / ultrawork / ultraqa loops** → nudge ON (condition 2).
|
|
138
|
+
- **Read-only `team status` / library one-shot use** → nudge OFF (no `enabled:true`,
|
|
139
|
+
no active loop mode).
|
|
140
|
+
|
|
141
|
+
### Tests
|
|
142
|
+
|
|
143
|
+
`test/team/runtime.test.ts` additions: nudge off by default; ON when `enabled:true`
|
|
144
|
+
(the `/team` path); ON when a ralph/loop mode state is active; OFF when neither signal
|
|
145
|
+
is present (e.g. `team status` polling).
|
|
146
|
+
|
|
147
|
+
## 5. Build sequence
|
|
148
|
+
|
|
149
|
+
1. `mailbox.ts` + tests (red→green).
|
|
150
|
+
2. `api.ts` messaging fns + `cli.ts` wiring + tests.
|
|
151
|
+
3. `worker-bootstrap.ts` overlay docs + test.
|
|
152
|
+
4. Nudge gating in `runtime.ts` + `mode-state` helper + tests.
|
|
153
|
+
5. Update `.github/skills/team/SKILL.md` to document messaging + nudge gating.
|
|
154
|
+
|
|
155
|
+
## 6. Out of scope (YAGNI)
|
|
156
|
+
|
|
157
|
+
- No dispatch-queue/retry state machine (the richer oh-my-claudecode design) — pull-based
|
|
158
|
+
mailbox + best-effort nudge is enough here.
|
|
159
|
+
- No leader registered as a team member; `leader` stays a reserved mailbox name.
|
|
160
|
+
- No cross-team messaging.
|
|
161
|
+
|
|
162
|
+
## 7. Resolved decisions
|
|
163
|
+
|
|
164
|
+
- **Broadcast reaches workers AND the lead** (every worker except sender + the `leader`
|
|
165
|
+
mailbox). Extends oh-my-claudecode's workers-only `teamBroadcast`.
|
|
166
|
+
- **Nudge throttling reuses existing mechanisms** (idle `NudgeTracker` scan/maxCount +
|
|
167
|
+
an unread-count in the send trigger, mirroring `generateMailboxTriggerMessage`).
|
|
168
|
+
No new debounce/coalesce layer.
|
|
169
|
+
- **Nudge gating:** off by default; ON for `/team` orchestration runs and active
|
|
170
|
+
ralph/ultrawork/ultraqa loop modes.
|
package/docs/self-evolve.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
A two-file mechanism that turns repeated user corrections into draft project skills.
|
|
4
4
|
|
|
5
|
-
-
|
|
5
|
+
- `.github/copilot-instructions.md` ships with the plugin and instructs the agent to invoke `/self-evolve` before ending a session — so the trigger fires in every project where the plugin is active, not only inside this repo.
|
|
6
6
|
- `.github/skills/self-evolve/SKILL.md` is the loop itself: log corrections to `.oh-my-copilot/self-evolve/log.md`, count repeats per topic, and when a topic recurs three times draft `.oh-my-copilot/self-evolve/drafts/<slug>/SKILL.md` with `status: draft`.
|
|
7
7
|
|
|
8
8
|
## Why drafts live outside `.github/skills/`
|
|
@@ -19,4 +19,4 @@ Move the draft directory from `.oh-my-copilot/self-evolve/drafts/<slug>/` to `.g
|
|
|
19
19
|
|
|
20
20
|
## Why agent-driven, not a CLI
|
|
21
21
|
|
|
22
|
-
Copilot CLI exposes no user-installable hook surface. The cheapest reliable trigger is the agent itself:
|
|
22
|
+
Copilot CLI exposes no user-installable hook surface. The cheapest reliable trigger is the agent itself: `.github/copilot-instructions.md` is loaded into every session where the plugin is active, and the instruction there ensures `/self-evolve` runs at wrap-up without any binary, dependency, or shell modification.
|
package/package.json
CHANGED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
const CACHE_TTL_MS = 6 * 60 * 60 * 1000;
|
|
6
|
+
const FETCH_TIMEOUT_MS = 2000;
|
|
7
|
+
const PACKAGE_NAME = "@damian87/omp";
|
|
8
|
+
|
|
9
|
+
export function isNewer(latest, current) {
|
|
10
|
+
if (!latest || !current) return false;
|
|
11
|
+
const [a = 0, b = 0, c = 0] = String(latest).split(".").map((n) => Number.parseInt(n, 10));
|
|
12
|
+
const [x = 0, y = 0, z = 0] = String(current).split(".").map((n) => Number.parseInt(n, 10));
|
|
13
|
+
if ([a, b, c, x, y, z].some(Number.isNaN)) return false;
|
|
14
|
+
if (a !== x) return a > x;
|
|
15
|
+
if (b !== y) return b > y;
|
|
16
|
+
return c > z;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function formatUpdateNotice(current, latest) {
|
|
20
|
+
return `[OMP UPDATE AVAILABLE]\n\nA new version of ${PACKAGE_NAME} is available: v${latest} (current: v${current})\nTo update, run: npm i -g ${PACKAGE_NAME}@latest`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function readCurrentVersion() {
|
|
24
|
+
try {
|
|
25
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
26
|
+
const pkgPath = join(here, "..", "..", "package.json");
|
|
27
|
+
return JSON.parse(readFileSync(pkgPath, "utf8")).version ?? null;
|
|
28
|
+
} catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function fetchLatestVersion() {
|
|
34
|
+
const controller = new AbortController();
|
|
35
|
+
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
36
|
+
try {
|
|
37
|
+
const res = await fetch(`https://registry.npmjs.org/${PACKAGE_NAME}/latest`, {
|
|
38
|
+
signal: controller.signal,
|
|
39
|
+
});
|
|
40
|
+
if (!res.ok) return null;
|
|
41
|
+
const data = await res.json();
|
|
42
|
+
return typeof data?.version === "string" ? data.version : null;
|
|
43
|
+
} catch {
|
|
44
|
+
return null;
|
|
45
|
+
} finally {
|
|
46
|
+
clearTimeout(timer);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function checkForUpdate({ stateDir, now = Date.now(), fetchLatest = fetchLatestVersion } = {}) {
|
|
51
|
+
const current = readCurrentVersion();
|
|
52
|
+
if (!current || !stateDir) return null;
|
|
53
|
+
|
|
54
|
+
const cachePath = join(stateDir, "version-check.json");
|
|
55
|
+
let latest = null;
|
|
56
|
+
|
|
57
|
+
if (existsSync(cachePath)) {
|
|
58
|
+
try {
|
|
59
|
+
const cache = JSON.parse(readFileSync(cachePath, "utf8"));
|
|
60
|
+
if (cache?.checkedAt && now - cache.checkedAt < CACHE_TTL_MS && typeof cache.latest === "string") {
|
|
61
|
+
latest = cache.latest;
|
|
62
|
+
}
|
|
63
|
+
} catch {
|
|
64
|
+
// ignore corrupt cache
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!latest) {
|
|
69
|
+
latest = await fetchLatest();
|
|
70
|
+
if (latest) {
|
|
71
|
+
try {
|
|
72
|
+
mkdirSync(stateDir, { recursive: true });
|
|
73
|
+
writeFileSync(cachePath, JSON.stringify({ checkedAt: now, latest }));
|
|
74
|
+
} catch {
|
|
75
|
+
// best-effort cache write
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!latest || !isNewer(latest, current)) return null;
|
|
81
|
+
return { current, latest };
|
|
82
|
+
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { appendFileSync, mkdirSync } from "node:fs";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { readStdin } from "./lib/stdin.mjs";
|
|
5
|
+
import { checkForUpdate, formatUpdateNotice } from "./lib/version-check.mjs";
|
|
5
6
|
|
|
6
7
|
const HOOK_NAME = "SessionStart";
|
|
7
8
|
|
|
@@ -11,7 +12,8 @@ const HOOK_NAME = "SessionStart";
|
|
|
11
12
|
const data = raw ? JSON.parse(raw) : {};
|
|
12
13
|
const sessionId = data.sessionId ?? data.session_id ?? "unknown";
|
|
13
14
|
const directory = data.directory ?? process.cwd();
|
|
14
|
-
const
|
|
15
|
+
const stateDir = join(directory, ".omp", "state");
|
|
16
|
+
const logFile = join(stateDir, "hooks.log");
|
|
15
17
|
mkdirSync(dirname(logFile), { recursive: true });
|
|
16
18
|
const line = JSON.stringify({
|
|
17
19
|
ts: new Date().toISOString(),
|
|
@@ -20,10 +22,14 @@ const HOOK_NAME = "SessionStart";
|
|
|
20
22
|
directory,
|
|
21
23
|
});
|
|
22
24
|
appendFileSync(logFile, `${line}\n`);
|
|
25
|
+
|
|
26
|
+
const update = await checkForUpdate({ stateDir });
|
|
27
|
+
const additionalContext = update ? formatUpdateNotice(update.current, update.latest) : "";
|
|
28
|
+
|
|
23
29
|
console.log(
|
|
24
30
|
JSON.stringify({
|
|
25
31
|
continue: true,
|
|
26
|
-
hookSpecificOutput: { hookEventName: HOOK_NAME, additionalContext
|
|
32
|
+
hookSpecificOutput: { hookEventName: HOOK_NAME, additionalContext },
|
|
27
33
|
}),
|
|
28
34
|
);
|
|
29
35
|
} catch (err) {
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: codebase-research
|
|
3
|
-
description: Read the repo first, map evidence, and return the smallest useful implementation context. Use with /codebase-research before planning or asking when repo facts matter.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Codebase Research
|
|
7
|
-
|
|
8
|
-
Use `/codebase-research` before planning or asking when repo facts matter.
|
|
9
|
-
|
|
10
|
-
Do:
|
|
11
|
-
- Find relevant files, symbols, tests, docs, and existing patterns.
|
|
12
|
-
- Separate evidence from inference.
|
|
13
|
-
- Name the likely change surface and risks.
|
|
14
|
-
- Ask nothing if the repo can answer it.
|
|
15
|
-
|
|
16
|
-
Output:
|
|
17
|
-
- `Evidence` — file paths and facts.
|
|
18
|
-
- `Likely path` — what should change.
|
|
19
|
-
- `Unknowns` — blockers only.
|
|
20
|
-
- `Next skill` — usually `/grill-me`, `/ralplan`, `/debug`, or `/tdd`.
|