@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.
Files changed (66) hide show
  1. package/.github/copilot-instructions.md +15 -0
  2. package/.github/skills/caveman/SKILL.md +22 -11
  3. package/.github/skills/code-review/SKILL.md +27 -13
  4. package/.github/skills/debug/SKILL.md +29 -13
  5. package/.github/skills/grill-me/SKILL.md +19 -7
  6. package/.github/skills/jira-ticket/SKILL.md +34 -12
  7. package/.github/skills/omp-autopilot/SKILL.md +67 -12
  8. package/.github/skills/prototype/SKILL.md +30 -12
  9. package/.github/skills/ralph/SKILL.md +62 -13
  10. package/.github/skills/ralplan/SKILL.md +21 -12
  11. package/.github/skills/research-codebase/SKILL.md +58 -0
  12. package/.github/skills/research-codebase/reference/agent-prompts.md +38 -0
  13. package/.github/skills/research-codebase/reference/follow-up.md +21 -0
  14. package/.github/skills/research-codebase/reference/permalink.md +26 -0
  15. package/.github/skills/research-codebase/reference/template.md +75 -0
  16. package/.github/skills/tdd/SKILL.md +27 -10
  17. package/.github/skills/team/SKILL.md +95 -13
  18. package/.github/skills/team/scripts/team-launch.sh +132 -0
  19. package/.github/skills/ultraqa/SKILL.md +55 -11
  20. package/.github/skills/ultrawork/SKILL.md +66 -13
  21. package/.github/skills/verify/SKILL.md +29 -11
  22. package/.github/skills/worktree/SKILL.md +51 -0
  23. package/README.md +28 -14
  24. package/catalog/capabilities.json +104 -12
  25. package/catalog/skills-general.json +62 -10
  26. package/dist/src/cli.js +30 -3
  27. package/dist/src/cli.js.map +1 -1
  28. package/dist/src/copilot/launch.js +42 -2
  29. package/dist/src/copilot/launch.js.map +1 -1
  30. package/dist/src/mode-state/paths.d.ts +6 -0
  31. package/dist/src/mode-state/paths.js +12 -0
  32. package/dist/src/mode-state/paths.js.map +1 -1
  33. package/dist/src/team/api.d.ts +60 -1
  34. package/dist/src/team/api.js +101 -0
  35. package/dist/src/team/api.js.map +1 -1
  36. package/dist/src/team/config.d.ts +7 -0
  37. package/dist/src/team/config.js +16 -0
  38. package/dist/src/team/config.js.map +1 -0
  39. package/dist/src/team/mailbox.d.ts +30 -0
  40. package/dist/src/team/mailbox.js +188 -0
  41. package/dist/src/team/mailbox.js.map +1 -0
  42. package/dist/src/team/runtime.d.ts +9 -1
  43. package/dist/src/team/runtime.js +18 -12
  44. package/dist/src/team/runtime.js.map +1 -1
  45. package/dist/src/team/types.d.ts +25 -0
  46. package/dist/src/team/worker-bootstrap.js +29 -0
  47. package/dist/src/team/worker-bootstrap.js.map +1 -1
  48. package/dist/test/catalog.test.d.ts +1 -0
  49. package/dist/test/catalog.test.js +21 -0
  50. package/dist/test/catalog.test.js.map +1 -0
  51. package/dist/test/jira.test.d.ts +1 -0
  52. package/dist/test/jira.test.js +26 -0
  53. package/dist/test/jira.test.js.map +1 -0
  54. package/dist/test/lint.test.d.ts +1 -0
  55. package/dist/test/lint.test.js +9 -0
  56. package/dist/test/lint.test.js.map +1 -0
  57. package/dist/test/sync.test.d.ts +1 -0
  58. package/dist/test/sync.test.js +15 -0
  59. package/dist/test/sync.test.js.map +1 -0
  60. package/docs/general-skills.md +5 -4
  61. package/docs/plans/2026-05-29-team-messaging-and-nudge-gating-design.md +170 -0
  62. package/docs/self-evolve.md +2 -2
  63. package/package.json +1 -1
  64. package/scripts/lib/version-check.mjs +82 -0
  65. package/scripts/session-start.mjs +8 -2
  66. package/.github/skills/codebase-research/SKILL.md +0 -20
@@ -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"}
@@ -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-research` | `codebase-research`, `research.codebase` | Read repo evidence before planning or asking. |
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 lanes. |
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
- | `/omc-autopilot` | `omc-autopilot`, `execution.autonomous` | Lightweight end-to-end flow across the other skills. (Renamed from `/autopilot` to avoid Copilot CLI built-in collision.) |
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-research
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.
@@ -2,7 +2,7 @@
2
2
 
3
3
  A two-file mechanism that turns repeated user corrections into draft project skills.
4
4
 
5
- - `AGENTS.md` instructs the agent to invoke `/self-evolve` before ending a session.
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: `AGENTS.md` is loaded into every session, and the instruction there ensures `/self-evolve` runs at wrap-up without any binary, dependency, or shell modification.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@damian87/omp",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "GitHub Copilot project skills catalog and Jira handoff tools.",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -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 logFile = join(directory, ".omp", "state", "hooks.log");
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`.