@plainconceptsplatform/agent-harness 2.0.0 → 2.0.1

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 (116) hide show
  1. package/README.md +423 -419
  2. package/package.json +3 -3
  3. package/src/commands/join.js +244 -244
  4. package/src/commands/shared.js +27 -27
  5. package/src/commands/single.js +79 -79
  6. package/src/commands/update.js +109 -109
  7. package/src/commands/wizard.js +134 -134
  8. package/src/content/.agents/skills/browser-automation/SKILL.md +66 -66
  9. package/src/content/.agents/skills/pc-guardrails-generic/SKILL.md +68 -68
  10. package/src/content/.agents/skills/pc-guardrails-project/SKILL.md +8 -8
  11. package/src/content/.agents/skills/pc-make-architecture/SKILL.md +51 -51
  12. package/src/content/.agents/skills/pc-make-architecture/structure-template.md +38 -38
  13. package/src/content/.agents/skills/pc-make-design/SKILL.md +68 -68
  14. package/src/content/.agents/skills/pc-make-engineer/SKILL.md +219 -219
  15. package/src/content/.agents/skills/pc-make-engineer/signal-mapping.md +68 -68
  16. package/src/content/.agents/skills/pc-make-engineer/template.md +81 -81
  17. package/src/content/.agents/skills/pc-make-evidence-scaffold/SKILL.md +18 -18
  18. package/src/content/.agents/skills/pc-make-evidence-scaffold/evidence-contract.md +29 -29
  19. package/src/content/.agents/skills/pc-make-guardrails/SKILL.md +74 -74
  20. package/src/content/.agents/skills/pc-make-guardrails/category-reference.md +68 -68
  21. package/src/content/.agents/skills/pc-make-merge-risk-assess/SKILL.md +70 -70
  22. package/src/content/.agents/skills/pc-make-merge-risk-assess/category-reference.md +98 -98
  23. package/src/content/.agents/skills/pc-make-user-model/SKILL.md +66 -66
  24. package/src/content/.agents/skills/pc-ops-evidence/SKILL.md +127 -127
  25. package/src/content/.agents/skills/pc-ops-ship/SKILL.md +18 -18
  26. package/src/content/.agents/skills/pc-plan-apply/SKILL.md +83 -83
  27. package/src/content/.agents/skills/pc-plan-apply/simple-mode.md +21 -21
  28. package/src/content/.agents/skills/pc-plan-archive/SKILL.md +63 -63
  29. package/src/content/.agents/skills/pc-plan-explore/SKILL.md +9 -9
  30. package/src/content/.agents/skills/pc-plan-goal/SKILL.md +94 -94
  31. package/src/content/.agents/skills/pc-plan-goal/branching.md +30 -30
  32. package/src/content/.agents/skills/pc-plan-goal/failure-policy.md +30 -30
  33. package/src/content/.agents/skills/pc-plan-goal/output-mode.md +9 -9
  34. package/src/content/.agents/skills/pc-plan-goal/output.md +68 -68
  35. package/src/content/.agents/skills/pc-plan-propose/SKILL.md +125 -125
  36. package/src/content/.agents/skills/pc-plan-propose/task-annotation.md +39 -39
  37. package/src/content/.agents/skills/pc-plan-quick/SKILL.md +62 -62
  38. package/src/content/.agents/skills/pc-plan-story/SKILL.md +146 -146
  39. package/src/content/.agents/skills/pc-repo-audit/SKILL.md +44 -44
  40. package/src/content/.agents/skills/pc-repo-help/SKILL.md +91 -91
  41. package/src/content/.agents/skills/pc-repo-initialize/SKILL.md +130 -130
  42. package/src/content/.agents/skills/pc-repo-onboard/SKILL.md +87 -87
  43. package/src/content/.agents/skills/pc-repo-verify/SKILL.md +34 -34
  44. package/src/content/.agents/skills/pc-userstory-az/SKILL.md +157 -157
  45. package/src/content/.agents/skills/pc-userstory-browser/SKILL.md +132 -132
  46. package/src/content/.agents/skills/pc-userstory-gh/SKILL.md +120 -120
  47. package/src/content/.agents/skills/pc-userstory-jira/SKILL.md +131 -131
  48. package/src/content/.opencode/_gitignore +7 -7
  49. package/src/content/.opencode/commands/init.md +5 -5
  50. package/src/content/.opencode/commands/make-architecture.md +5 -5
  51. package/src/content/.opencode/commands/make-design.md +5 -5
  52. package/src/content/.opencode/commands/make-engineer.md +5 -5
  53. package/src/content/.opencode/commands/make-evidence-scaffold.md +5 -5
  54. package/src/content/.opencode/commands/make-guardrails.md +5 -5
  55. package/src/content/.opencode/commands/make-user-model.md +5 -5
  56. package/src/content/.opencode/commands/ops-backlog.md +10 -10
  57. package/src/content/.opencode/commands/ops-evidence.md +9 -9
  58. package/src/content/.opencode/commands/ops-review.md +8 -8
  59. package/src/content/.opencode/commands/ops-ship.md +9 -9
  60. package/src/content/.opencode/commands/plan-apply.md +9 -9
  61. package/src/content/.opencode/commands/plan-archive.md +5 -5
  62. package/src/content/.opencode/commands/plan-explore.md +9 -9
  63. package/src/content/.opencode/commands/plan-goal.md +5 -5
  64. package/src/content/.opencode/commands/plan-propose.md +9 -9
  65. package/src/content/.opencode/commands/plan-quick.md +5 -5
  66. package/src/content/.opencode/commands/plan-story.md +9 -9
  67. package/src/content/.opencode/commands/repo-audit.md +5 -5
  68. package/src/content/.opencode/commands/repo-help.md +5 -5
  69. package/src/content/.opencode/commands/repo-initialize.md +5 -5
  70. package/src/content/.opencode/commands/repo-onboard.md +5 -5
  71. package/src/content/.opencode/commands/repo-verify.md +5 -5
  72. package/src/content/.opencode/plugins/pc-subagent-monitor.js +139 -139
  73. package/src/content/.opencode/plugins/pc-subagent-tiers.js +179 -179
  74. package/src/content/.opencode/plugins/pc-system-reminders.js +96 -96
  75. package/src/content/.opencode/tui/pc-subagents.tsx +98 -98
  76. package/src/content/.opencode/tui.json +6 -6
  77. package/src/content/AGENTS.md +71 -71
  78. package/src/fragments/archive/az.md +95 -95
  79. package/src/fragments/archive/gh.md +94 -94
  80. package/src/fragments/archive/gl.md +94 -94
  81. package/src/fragments/archive/none.md +73 -73
  82. package/src/fragments/guardrails/codegraph.md +7 -7
  83. package/src/fragments/guardrails/humanizer.md +4 -4
  84. package/src/fragments/guardrails/memory.md +4 -4
  85. package/src/fragments/guardrails/rtk.md +3 -3
  86. package/src/fragments/guardrails/simple-english.md +4 -4
  87. package/src/fragments/ops-backlog/az.md +28 -28
  88. package/src/fragments/ops-backlog/gh.md +29 -29
  89. package/src/fragments/ops-backlog/jira.md +28 -28
  90. package/src/fragments/ops-evidence/az.md +41 -41
  91. package/src/fragments/ops-evidence/gh.md +53 -53
  92. package/src/fragments/ops-evidence/jira.md +38 -38
  93. package/src/fragments/ops-review/az.md +62 -62
  94. package/src/fragments/ops-review/gh.md +52 -52
  95. package/src/fragments/ops-review/gl.md +56 -56
  96. package/src/fragments/ops-ship/az.md +80 -80
  97. package/src/fragments/ops-ship/gh.md +68 -68
  98. package/src/fragments/ops-ship/gl.md +85 -85
  99. package/src/index.js +107 -107
  100. package/src/presets/agents-content.json +53 -53
  101. package/src/presets/models.json +68 -68
  102. package/src/steps/copy/agents.js +118 -118
  103. package/src/steps/copy/commands.js +91 -91
  104. package/src/steps/copy/fullstack-engineer.js +83 -83
  105. package/src/steps/copy/index.js +88 -88
  106. package/src/steps/copy/opencode-json.js +129 -129
  107. package/src/steps/copy/skills.js +196 -196
  108. package/src/steps/metadata/index.js +108 -108
  109. package/src/steps/models/write.js +34 -34
  110. package/src/steps/optimization/patch-guardrails.js +108 -108
  111. package/src/utils/copy.js +108 -108
  112. package/src/utils/legacy-check.js +30 -30
  113. package/src/utils/models-cache.js +58 -58
  114. package/src/utils/paths.js +64 -64
  115. package/src/utils/update-manifest.js +49 -49
  116. package/src/content/.opencode/plugins/pc-system-reminders.test.js +0 -35
@@ -1,58 +1,58 @@
1
- import fse from 'fs-extra'
2
- import path from 'path'
3
- import { parseModels } from './models-pricing.js'
4
- import { modelsCacheDir } from './paths.js'
5
-
6
- const CACHE_DIR = modelsCacheDir();
7
- const CACHE_FILE = path.join(CACHE_DIR, 'models-cache.json');
8
- const CACHE_TTL_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
9
- const MODELS_URL = 'https://models.dev/api.json';
10
-
11
- async function loadCache() {
12
- try {
13
- if (!await fse.pathExists(CACHE_FILE)) return null;
14
- const cache = await fse.readJson(CACHE_FILE);
15
- if (!cache.timestamp || !cache.models) return null;
16
- const age = Date.now() - cache.timestamp;
17
- if (age > CACHE_TTL_MS) return null; // expired
18
- return cache.models;
19
- } catch {
20
- return null;
21
- }
22
- }
23
-
24
- async function saveCache(models) {
25
- try {
26
- await fse.ensureDir(CACHE_DIR);
27
- await fse.writeJson(CACHE_FILE, { timestamp: Date.now(), models });
28
- } catch {
29
- // cache write failure is non-fatal
30
- }
31
- }
32
-
33
- export async function fetchModels() {
34
- // 1. Try cache first (fresh)
35
- const cached = await loadCache();
36
- if (cached) return { models: cached, source: 'cache' };
37
-
38
- // 2. Try network
39
- try {
40
- const res = await fetch(MODELS_URL, { signal: AbortSignal.timeout(8000) });
41
- if (!res.ok) throw new Error(`HTTP ${res.status}`);
42
- const data = await res.json();
43
- const models = parseModels(data);
44
- await saveCache(models);
45
- return { models, source: 'network' };
46
- } catch {
47
- // 3. Network failed, fall back to stale cache if available
48
- try {
49
- if (await fse.pathExists(CACHE_FILE)) {
50
- const cache = await fse.readJson(CACHE_FILE);
51
- if (cache.models?.length) return { models: cache.models, source: 'stale-cache' };
52
- }
53
- } catch {
54
- // ignore
55
- }
56
- return { models: null, source: 'unavailable' };
57
- }
58
- }
1
+ import fse from 'fs-extra'
2
+ import path from 'path'
3
+ import { parseModels } from './models-pricing.js'
4
+ import { modelsCacheDir } from './paths.js'
5
+
6
+ const CACHE_DIR = modelsCacheDir();
7
+ const CACHE_FILE = path.join(CACHE_DIR, 'models-cache.json');
8
+ const CACHE_TTL_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
9
+ const MODELS_URL = 'https://models.dev/api.json';
10
+
11
+ async function loadCache() {
12
+ try {
13
+ if (!await fse.pathExists(CACHE_FILE)) return null;
14
+ const cache = await fse.readJson(CACHE_FILE);
15
+ if (!cache.timestamp || !cache.models) return null;
16
+ const age = Date.now() - cache.timestamp;
17
+ if (age > CACHE_TTL_MS) return null; // expired
18
+ return cache.models;
19
+ } catch {
20
+ return null;
21
+ }
22
+ }
23
+
24
+ async function saveCache(models) {
25
+ try {
26
+ await fse.ensureDir(CACHE_DIR);
27
+ await fse.writeJson(CACHE_FILE, { timestamp: Date.now(), models });
28
+ } catch {
29
+ // cache write failure is non-fatal
30
+ }
31
+ }
32
+
33
+ export async function fetchModels() {
34
+ // 1. Try cache first (fresh)
35
+ const cached = await loadCache();
36
+ if (cached) return { models: cached, source: 'cache' };
37
+
38
+ // 2. Try network
39
+ try {
40
+ const res = await fetch(MODELS_URL, { signal: AbortSignal.timeout(8000) });
41
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
42
+ const data = await res.json();
43
+ const models = parseModels(data);
44
+ await saveCache(models);
45
+ return { models, source: 'network' };
46
+ } catch {
47
+ // 3. Network failed, fall back to stale cache if available
48
+ try {
49
+ if (await fse.pathExists(CACHE_FILE)) {
50
+ const cache = await fse.readJson(CACHE_FILE);
51
+ if (cache.models?.length) return { models: cache.models, source: 'stale-cache' };
52
+ }
53
+ } catch {
54
+ // ignore
55
+ }
56
+ return { models: null, source: 'unavailable' };
57
+ }
58
+ }
@@ -1,64 +1,64 @@
1
- import os from 'node:os'
2
- import path from 'node:path'
3
-
4
- /**
5
- * Every harness path that lands in a consumer repo, in one place.
6
- *
7
- * These filenames are a contract with every repo the harness has been
8
- * installed into: the CLI writes them, and the opencode plugins shipped in
9
- * src/content/.opencode/plugins/ read them back at runtime. Changing a name
10
- * here means changing it in that payload too, or the plugins go blind.
11
- *
12
- * They are deliberately named for the harness rather than for the CLI, so a
13
- * future rename of the package does not become a rename of the on-disk state.
14
- */
15
- export const OPENCODE_DIR = '.opencode'
16
-
17
- /** Project config written by the metadata step. */
18
- export const CONFIG_FILE = 'harness.json'
19
-
20
- /** Per-developer model override, gitignored, never committed. */
21
- export const USER_CONFIG_FILE = 'harness.user.json'
22
-
23
- /** Hashes of every managed file, so `update` can detect local edits. */
24
- export const MANIFEST_FILE = 'harness-managed.json'
25
-
26
- /** Live subagent wave state, owned by the pc-subagent-monitor plugin. */
27
- export const RUN_STATE_FILE = 'harness-run.json'
28
-
29
- /** Names `.opencode/.gitignore` must cover. Mirrored in content/.opencode/_gitignore. */
30
- export const IGNORED_ENTRIES = [
31
- 'node_modules',
32
- RUN_STATE_FILE,
33
- USER_CONFIG_FILE,
34
- MANIFEST_FILE,
35
- 'source-roots.json',
36
- '*-engineer.*.md',
37
- ]
38
-
39
- export function configPath(cwd = process.cwd()) {
40
- return path.join(cwd, OPENCODE_DIR, CONFIG_FILE)
41
- }
42
-
43
- export function userConfigPath(cwd = process.cwd()) {
44
- return path.join(cwd, OPENCODE_DIR, USER_CONFIG_FILE)
45
- }
46
-
47
- export function manifestPath(cwd = process.cwd()) {
48
- return path.join(cwd, OPENCODE_DIR, MANIFEST_FILE)
49
- }
50
-
51
- /** Cross-run models cache, keyed to the CLI rather than to any one repo. */
52
- export function modelsCacheDir() {
53
- return path.join(os.homedir(), '.config', 'agent-harness')
54
- }
55
-
56
- /**
57
- * Filenames written by opencode-onboard v1.x. Only used to detect a v1 repo
58
- * and refuse to half-patch it: nothing reads their contents.
59
- */
60
- export const LEGACY_CONFIG_FILES = [
61
- 'opencode-onboard.json',
62
- 'opencode-onboard.user.json',
63
- 'opencode-onboard-managed.json',
64
- ]
1
+ import os from 'node:os'
2
+ import path from 'node:path'
3
+
4
+ /**
5
+ * Every harness path that lands in a consumer repo, in one place.
6
+ *
7
+ * These filenames are a contract with every repo the harness has been
8
+ * installed into: the CLI writes them, and the opencode plugins shipped in
9
+ * src/content/.opencode/plugins/ read them back at runtime. Changing a name
10
+ * here means changing it in that payload too, or the plugins go blind.
11
+ *
12
+ * They are deliberately named for the harness rather than for the CLI, so a
13
+ * future rename of the package does not become a rename of the on-disk state.
14
+ */
15
+ export const OPENCODE_DIR = '.opencode'
16
+
17
+ /** Project config written by the metadata step. */
18
+ export const CONFIG_FILE = 'harness.json'
19
+
20
+ /** Per-developer model override, gitignored, never committed. */
21
+ export const USER_CONFIG_FILE = 'harness.user.json'
22
+
23
+ /** Hashes of every managed file, so `update` can detect local edits. */
24
+ export const MANIFEST_FILE = 'harness-managed.json'
25
+
26
+ /** Live subagent wave state, owned by the pc-subagent-monitor plugin. */
27
+ export const RUN_STATE_FILE = 'harness-run.json'
28
+
29
+ /** Names `.opencode/.gitignore` must cover. Mirrored in content/.opencode/_gitignore. */
30
+ export const IGNORED_ENTRIES = [
31
+ 'node_modules',
32
+ RUN_STATE_FILE,
33
+ USER_CONFIG_FILE,
34
+ MANIFEST_FILE,
35
+ 'source-roots.json',
36
+ '*-engineer.*.md',
37
+ ]
38
+
39
+ export function configPath(cwd = process.cwd()) {
40
+ return path.join(cwd, OPENCODE_DIR, CONFIG_FILE)
41
+ }
42
+
43
+ export function userConfigPath(cwd = process.cwd()) {
44
+ return path.join(cwd, OPENCODE_DIR, USER_CONFIG_FILE)
45
+ }
46
+
47
+ export function manifestPath(cwd = process.cwd()) {
48
+ return path.join(cwd, OPENCODE_DIR, MANIFEST_FILE)
49
+ }
50
+
51
+ /** Cross-run models cache, keyed to the CLI rather than to any one repo. */
52
+ export function modelsCacheDir() {
53
+ return path.join(os.homedir(), '.config', 'agent-harness')
54
+ }
55
+
56
+ /**
57
+ * Filenames written by opencode-onboard v1.x. Only used to detect a v1 repo
58
+ * and refuse to half-patch it: nothing reads their contents.
59
+ */
60
+ export const LEGACY_CONFIG_FILES = [
61
+ 'opencode-onboard.json',
62
+ 'opencode-onboard.user.json',
63
+ 'opencode-onboard-managed.json',
64
+ ]
@@ -1,49 +1,49 @@
1
- import crypto from 'node:crypto'
2
- import fse from 'fs-extra'
3
- import path from 'node:path'
4
- import { MANIFEST_FILE, OPENCODE_DIR } from './paths.js'
5
-
6
- const MANIFEST_RELATIVE_PATH = path.join(OPENCODE_DIR, MANIFEST_FILE)
7
-
8
- function normalizeRelativePath(relativePath) {
9
- return relativePath.split(path.sep).join('/')
10
- }
11
-
12
- function hashContent(content) {
13
- return crypto.createHash('sha256').update(content).digest('hex')
14
- }
15
-
16
- export async function hashFile(filePath) {
17
- return hashContent(await fse.readFile(filePath))
18
- }
19
-
20
- export async function readUpdateManifest(cwd = process.cwd()) {
21
- const manifestPath = path.join(cwd, MANIFEST_RELATIVE_PATH)
22
- const manifest = await fse.readJson(manifestPath).catch(() => ({ version: 1, files: {} }))
23
- if (!manifest.files || typeof manifest.files !== 'object') manifest.files = {}
24
- return manifest
25
- }
26
-
27
- export async function writeUpdateManifest(manifest, cwd = process.cwd()) {
28
- const manifestPath = path.join(cwd, MANIFEST_RELATIVE_PATH)
29
- await fse.ensureDir(path.dirname(manifestPath))
30
- await fse.writeJson(manifestPath, manifest, { spaces: 2 })
31
- }
32
-
33
- export async function canUpdateManagedFile(relativePath, cwd, manifest) {
34
- const normalizedPath = normalizeRelativePath(relativePath)
35
- const destinationPath = path.join(cwd, relativePath)
36
- if (!await fse.pathExists(destinationPath)) return true
37
- const previousHash = manifest.files?.[normalizedPath]
38
- if (!previousHash) return false
39
- return previousHash === await hashFile(destinationPath)
40
- }
41
-
42
- export async function recordManagedFile(manifest, relativePath, sourcePath) {
43
- const files = manifest.files
44
- files[normalizeRelativePath(relativePath)] = await hashFile(sourcePath)
45
- }
46
-
47
- export function manifestPath() {
48
- return MANIFEST_RELATIVE_PATH
49
- }
1
+ import crypto from 'node:crypto'
2
+ import fse from 'fs-extra'
3
+ import path from 'node:path'
4
+ import { MANIFEST_FILE, OPENCODE_DIR } from './paths.js'
5
+
6
+ const MANIFEST_RELATIVE_PATH = path.join(OPENCODE_DIR, MANIFEST_FILE)
7
+
8
+ function normalizeRelativePath(relativePath) {
9
+ return relativePath.split(path.sep).join('/')
10
+ }
11
+
12
+ function hashContent(content) {
13
+ return crypto.createHash('sha256').update(content).digest('hex')
14
+ }
15
+
16
+ export async function hashFile(filePath) {
17
+ return hashContent(await fse.readFile(filePath))
18
+ }
19
+
20
+ export async function readUpdateManifest(cwd = process.cwd()) {
21
+ const manifestPath = path.join(cwd, MANIFEST_RELATIVE_PATH)
22
+ const manifest = await fse.readJson(manifestPath).catch(() => ({ version: 1, files: {} }))
23
+ if (!manifest.files || typeof manifest.files !== 'object') manifest.files = {}
24
+ return manifest
25
+ }
26
+
27
+ export async function writeUpdateManifest(manifest, cwd = process.cwd()) {
28
+ const manifestPath = path.join(cwd, MANIFEST_RELATIVE_PATH)
29
+ await fse.ensureDir(path.dirname(manifestPath))
30
+ await fse.writeJson(manifestPath, manifest, { spaces: 2 })
31
+ }
32
+
33
+ export async function canUpdateManagedFile(relativePath, cwd, manifest) {
34
+ const normalizedPath = normalizeRelativePath(relativePath)
35
+ const destinationPath = path.join(cwd, relativePath)
36
+ if (!await fse.pathExists(destinationPath)) return true
37
+ const previousHash = manifest.files?.[normalizedPath]
38
+ if (!previousHash) return false
39
+ return previousHash === await hashFile(destinationPath)
40
+ }
41
+
42
+ export async function recordManagedFile(manifest, relativePath, sourcePath) {
43
+ const files = manifest.files
44
+ files[normalizeRelativePath(relativePath)] = await hashFile(sourcePath)
45
+ }
46
+
47
+ export function manifestPath() {
48
+ return MANIFEST_RELATIVE_PATH
49
+ }
@@ -1,35 +0,0 @@
1
- import { describe, expect, it } from "vitest"
2
- import { PcSystemReminders } from "./pc-system-reminders.js"
3
-
4
- describe("PcSystemReminders", () => {
5
- it("reminds an agent to load its abilities before work", async () => {
6
- const plugin = await PcSystemReminders({ directory: process.cwd() })
7
- const output = {
8
- messages: [{
9
- info: { role: "user", sessionID: "session-1", agent: "missing-agent" },
10
- parts: [{ type: "text", text: "Implement the change" }],
11
- }],
12
- }
13
-
14
- await plugin["chat.message"]({ sessionID: "session-1", agent: "missing-agent" })
15
- await plugin["experimental.chat.messages.transform"]({}, output)
16
-
17
- expect(output.messages[0].parts[0].text).toContain("pc-guardrails-generic")
18
- })
19
-
20
- it("stops reminding after the required guardrail loads", async () => {
21
- const plugin = await PcSystemReminders({ directory: process.cwd() })
22
- const output = {
23
- messages: [{
24
- info: { role: "user", sessionID: "session-2", agent: "missing-agent" },
25
- parts: [{ type: "text", text: "Implement the change" }],
26
- }],
27
- }
28
-
29
- await plugin["chat.message"]({ sessionID: "session-2", agent: "missing-agent" })
30
- await plugin["tool.execute.after"]({ tool: "skill", sessionID: "session-2", args: { name: "pc-guardrails-generic" } })
31
- await plugin["experimental.chat.messages.transform"]({}, output)
32
-
33
- expect(output.messages[0].parts[0].text).not.toContain("system-reminder")
34
- })
35
- })