@manhd_d/ai-harness 0.7.0 → 0.8.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/CHANGELOG.md +39 -0
- package/CONTRIBUTING.md +4 -0
- package/README.md +15 -3
- package/adapters/java-spring/adapter.yaml +3 -2
- package/adapters/java-spring/checks/verification.yaml +12 -0
- package/adapters/java-spring/evals/cases.json +10 -0
- package/adapters/java-spring/rules/conventions.md +39 -0
- package/adapters/java-spring/rules.md +2 -1
- package/adapters/kubernetes/adapter.yaml +1 -1
- package/adapters/kubernetes/rules.md +3 -0
- package/adapters/react-native/adapter.yaml +3 -2
- package/adapters/react-native/checks/verification.yaml +12 -0
- package/adapters/react-native/evals/cases.json +10 -0
- package/adapters/react-native/rules/conventions.md +34 -0
- package/adapters/react-native/rules.md +2 -1
- package/adapters/react-nextjs/adapter.yaml +3 -2
- package/adapters/react-nextjs/checks/verification.yaml +12 -0
- package/adapters/react-nextjs/evals/cases.json +10 -0
- package/adapters/react-nextjs/rules/conventions.md +37 -0
- package/adapters/react-nextjs/rules.md +2 -1
- package/capabilities/architecture/README.md +11 -0
- package/capabilities/architecture/agents/architecture-reviewer.md +16 -0
- package/capabilities/architecture/agents/solution-architect.md +16 -0
- package/capabilities/architecture/capability.json +35 -0
- package/capabilities/architecture/checks/verification.json +29 -0
- package/capabilities/architecture/evals/cases.json +35 -0
- package/capabilities/architecture/skills/harness-architecture-change-impact/SKILL.md +43 -0
- package/capabilities/architecture/skills/harness-architecture-decision-records/SKILL.md +43 -0
- package/capabilities/architecture/skills/harness-architecture-project-context/SKILL.md +43 -0
- package/capabilities/architecture/skills/harness-architecture-quality-attributes/SKILL.md +43 -0
- package/capabilities/architecture/skills/harness-architecture-review/SKILL.md +43 -0
- package/capabilities/architecture/skills/harness-architecture-solution-design/SKILL.md +44 -0
- package/capabilities/architecture/templates/adr.md +29 -0
- package/capabilities/architecture/templates/nfr.md +22 -0
- package/capabilities/architecture/templates/overview.md +24 -0
- package/capabilities/architecture/templates/project-map.md +26 -0
- package/capabilities/architecture/templates/review.md +23 -0
- package/capabilities/architecture/templates/solution-design.md +29 -0
- package/capabilities/architecture/workflows/change-review.md +12 -0
- package/capabilities/architecture/workflows/discovery-to-design.md +16 -0
- package/capabilities/delivery/README.md +11 -0
- package/capabilities/delivery/agents/project-manager.md +16 -0
- package/capabilities/delivery/capability.json +29 -0
- package/capabilities/delivery/checks/verification.json +29 -0
- package/capabilities/delivery/evals/cases.json +25 -0
- package/capabilities/delivery/skills/harness-delivery-planning/SKILL.md +43 -0
- package/capabilities/delivery/skills/harness-delivery-risk-management/SKILL.md +42 -0
- package/capabilities/delivery/skills/harness-delivery-scope-change/SKILL.md +43 -0
- package/capabilities/delivery/templates/change-assessment.md +23 -0
- package/capabilities/delivery/templates/delivery-plan.md +25 -0
- package/capabilities/delivery/templates/risk-register.md +19 -0
- package/capabilities/delivery/workflows/execution-change.md +9 -0
- package/capabilities/delivery/workflows/planning.md +12 -0
- package/capabilities/product/README.md +7 -0
- package/capabilities/product/agents/business-analyst.md +3 -0
- package/capabilities/product/agents/product-owner.md +5 -1
- package/capabilities/product/agents/project-manager.md +5 -0
- package/capabilities/product/capability.json +9 -4
- package/capabilities/product/checks/verification.json +12 -0
- package/capabilities/product/evals/cases.json +10 -0
- package/capabilities/product/skills/harness-product-acceptance-criteria/SKILL.md +17 -0
- package/capabilities/product/skills/harness-product-backlog-prioritization/SKILL.md +17 -0
- package/capabilities/product/skills/harness-product-brd-generation/SKILL.md +17 -0
- package/capabilities/product/skills/harness-product-business-analysis/SKILL.md +17 -0
- package/capabilities/product/skills/harness-product-document-review/SKILL.md +17 -0
- package/capabilities/product/skills/harness-product-idea-discovery/SKILL.md +17 -0
- package/capabilities/product/skills/harness-product-knowledge-analysis/SKILL.md +17 -0
- package/capabilities/product/skills/harness-product-mvp-definition/SKILL.md +43 -0
- package/capabilities/product/skills/harness-product-prd-generation/SKILL.md +17 -0
- package/capabilities/product/skills/harness-product-product-outcomes/SKILL.md +42 -0
- package/capabilities/product/skills/harness-product-product-research/SKILL.md +17 -0
- package/capabilities/product/skills/harness-product-srs-generation/SKILL.md +17 -0
- package/capabilities/product/skills/harness-product-traceability/SKILL.md +17 -0
- package/capabilities/product/skills/harness-product-use-case-generation/SKILL.md +17 -0
- package/capabilities/product/skills/harness-product-user-stories/SKILL.md +17 -0
- package/capabilities/product/templates/mvp.md +25 -0
- package/capabilities/product/templates/outcomes.md +23 -0
- package/capabilities/product/workflows/document-driven.md +4 -0
- package/capabilities/product/workflows/idea-driven.md +4 -0
- package/capabilities/product/workflows/product-to-solution.md +15 -0
- package/cli/agents.js +165 -0
- package/cli/environment.js +14 -1
- package/cli/harness.js +24 -9
- package/cli/main.js +19 -6
- package/config/agents.default.json +80 -0
- package/core/agents/documentation.md +13 -0
- package/core/agents/implementer.md +7 -0
- package/core/agents/orchestrator.md +13 -0
- package/core/agents/planner.md +4 -0
- package/core/agents/reviewer.md +8 -0
- package/core/checks/verification.json +37 -0
- package/core/evals/cases.json +70 -0
- package/core/evals/composition.json +15 -0
- package/core/rules/foundations.md +1 -0
- package/core/rules/project-conventions.md +61 -0
- package/core/workflows/agent-routing.md +28 -0
- package/core/workflows/bugfix.md +2 -0
- package/core/workflows/project-setup.md +9 -1
- package/core/workflows/solution-framing.md +33 -0
- package/core/workflows/spec-to-code.md +11 -3
- package/docs/adapters.md +6 -0
- package/docs/agent-configuration.md +99 -0
- package/docs/agent-handoffs.md +19 -0
- package/docs/architecture.md +22 -1
- package/docs/cli.md +23 -1
- package/docs/documentation-workflows.md +7 -0
- package/docs/end-to-end-workflow.md +10 -1
- package/docs/getting-started.md +17 -0
- package/docs/harness-maintenance.md +80 -0
- package/docs/monetization.md +1 -1
- package/docs/project-configuration.md +4 -0
- package/docs/project-conventions.md +67 -0
- package/docs/research/solution-workflow-sources.md +30 -0
- package/docs/skill-matrix.md +28 -4
- package/docs/solution-workflow.md +67 -0
- package/docs/templates/harness-change-impact.md +30 -0
- package/docs/tool-setup/README.md +70 -0
- package/docs/tool-setup/claude-code.md +57 -0
- package/docs/tool-setup/codex.md +44 -0
- package/docs/tool-setup/copilot.md +42 -0
- package/docs/tool-setup/cursor.md +44 -0
- package/docs/tool-setup/gemini-cli.md +41 -0
- package/docs/versioning.md +29 -0
- package/evals/README.md +13 -0
- package/harness.yaml +4 -2
- package/package.json +2 -1
- package/templates/AGENTS.md +7 -0
- package/templates/openspec/design.md +6 -0
- package/templates/openspec/tasks.md +1 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# MVP scope
|
|
2
|
+
|
|
3
|
+
Status: draft / pending review
|
|
4
|
+
Owner: TODO
|
|
5
|
+
Sources and revisions: TODO
|
|
6
|
+
Scope / related requirement and decision IDs: TODO
|
|
7
|
+
Last verified against code: TODO (or not applicable with reason)
|
|
8
|
+
|
|
9
|
+
## User and outcome
|
|
10
|
+
Target actor, problem, value-bearing end-to-end journey and validation evidence.
|
|
11
|
+
## Scope cuts
|
|
12
|
+
| Capability | In / out | Value / dependency | Requirement / AC | Decision source |
|
|
13
|
+
| --- | --- | --- | --- | --- |
|
|
14
|
+
| TODO | TODO | TODO | TODO | TODO |
|
|
15
|
+
## Minimum viable quality
|
|
16
|
+
Authorization, data integrity, failure recovery and relevant NFRs that cannot be deferred.
|
|
17
|
+
## Acceptance and learning
|
|
18
|
+
Observable journey completion, success signals, measurement owner, known baseline/target or unknown.
|
|
19
|
+
## Open decisions
|
|
20
|
+
Options/trade-offs, recommended cut with evidence and actual owner answer; unanswered choices remain pending.
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
## Review and handoff
|
|
24
|
+
Record actual owner decisions, unresolved consequential questions, verification performed/skipped,
|
|
25
|
+
next role, source/output paths and affected OpenSpec change. Draft text is not an accepted decision.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Product outcomes
|
|
2
|
+
|
|
3
|
+
Status: draft / pending review
|
|
4
|
+
Owner: TODO
|
|
5
|
+
Sources and revisions: TODO
|
|
6
|
+
Scope / related requirement and decision IDs: TODO
|
|
7
|
+
Last verified against code: TODO (or not applicable with reason)
|
|
8
|
+
|
|
9
|
+
## Value hypothesis
|
|
10
|
+
User problem and expected benefit; link MVP/PRD instead of duplicating scope.
|
|
11
|
+
## Measurement plan
|
|
12
|
+
| Outcome / metric | Definition / source | Baseline | Proposed target / authority | Owner / period |
|
|
13
|
+
| --- | --- | --- | --- | --- |
|
|
14
|
+
| TODO | TODO | unknown | pending | TODO |
|
|
15
|
+
## Experiment and guardrails
|
|
16
|
+
Hypothesis, smallest experiment, cost/privacy limits, negative effects and decision checkpoint.
|
|
17
|
+
## Evidence and next action
|
|
18
|
+
Separate observed results from forecasts; propose continue/pivot/stop without self-approving scope changes.
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
## Review and handoff
|
|
22
|
+
Record actual owner decisions, unresolved consequential questions, verification performed/skipped,
|
|
23
|
+
next role, source/output paths and affected OpenSpec change. Draft text is not an accepted decision.
|
|
@@ -11,3 +11,7 @@ Use when a project already supplies authorized BA/PO materials (Confluence, BRD,
|
|
|
11
11
|
7. **Source changes:** compare actual source revisions and propose a reviewed requirements/code impact diff rather than overwriting user-owned artifacts.
|
|
12
12
|
|
|
13
13
|
Canonical enterprise source stays in the enterprise system; consumer repo stores pointers, versioned derived docs and project-local engineering artifacts. RAG is optional permission-scoped retrieval, not a replacement for a system of record. The `aih` CLI does not ingest data or run agents.
|
|
14
|
+
|
|
15
|
+
For new/material journeys, continue with `product-to-solution.md` after resolving the relevant scope.
|
|
16
|
+
Use reviewed MVP/outcomes and optional Architecture/Delivery to assess solution/dependencies before
|
|
17
|
+
OpenSpec implementation. Existing approved sources remain canonical; small clear changes stay compact.
|
|
@@ -11,3 +11,7 @@ Use this when the developer starts with an idea, including a solo project. Produ
|
|
|
11
11
|
7. **Changes:** when the idea changes, perform impact analysis and propose minimal document diffs; never silently overwrite approved requirements.
|
|
12
12
|
|
|
13
13
|
Deliverables are **consumer-owned** `docs/discovery/`, `docs/product/` and `openspec/` files. For Confluence publishing, preserve URLs/page versions and request explicit write permission; the Harness CLI does not connect or publish to Confluence.
|
|
14
|
+
|
|
15
|
+
For new/material journeys, continue with `product-to-solution.md` after resolving the relevant scope.
|
|
16
|
+
Use reviewed MVP/outcomes and optional Architecture/Delivery to assess solution/dependencies before
|
|
17
|
+
OpenSpec implementation. Existing approved sources remain canonical; small clear changes stay compact.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# BA → PO → Solution → Delivery
|
|
2
|
+
|
|
3
|
+
Use for a new product or material feature. For a small clear fix, use the Core compact path.
|
|
4
|
+
1. BA preserves idea/approved document revisions, identifies users, business rules and contradictions;
|
|
5
|
+
load existing business-analysis/use-case/AC skills. Source/output register is authoritative.
|
|
6
|
+
2. PO uses mvp-definition and product-outcomes to propose a complete journey, in/out scope, minimum
|
|
7
|
+
quality and success evidence. Resolve consequential unknowns with actual owner; no self-approval.
|
|
8
|
+
3. Pass reviewed FR/US/AC/NFR and decision status to installed Architecture discovery-to-design.
|
|
9
|
+
If Architecture is not selected, Core spec uses the same constraints and records relevant choices.
|
|
10
|
+
4. Pass accepted solution boundaries, AC, dependencies and actual estimates to optional Delivery planning;
|
|
11
|
+
absent Delivery, record a proportional task order in the OpenSpec change.
|
|
12
|
+
5. UI/UX can iterate with SA/PO; adapters implement accepted technical decisions. Verify and review
|
|
13
|
+
against requirements/ADRs. Evidence and scope changes loop back to the owning role.
|
|
14
|
+
6. Carry source revisions, IDs, output paths, decisions, unresolved questions, actual checks and next
|
|
15
|
+
owner in every handoff. A role label never creates a worker or approves a stakeholder decision.
|
package/cli/agents.js
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { safe, stat, readJson, isObject, SLUG, SOURCE, HarnessError } from './files.js';
|
|
4
|
+
|
|
5
|
+
export const AGENT_CONFIG = '.harness/agents.json';
|
|
6
|
+
export const DEFAULT_AGENTS = 'config/agents.default.json';
|
|
7
|
+
export const PRESETS = ['recommended', 'economy', 'custom'];
|
|
8
|
+
export const REASONING = ['none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max', 'ultra'];
|
|
9
|
+
const ROLE = /^[a-z][a-z0-9-]*:[a-z][a-z0-9-]*$/;
|
|
10
|
+
const text = value => typeof value === 'string' && value.trim() === value && value.length > 0
|
|
11
|
+
&& value.length <= 256 && !/[\x00-\x1f\x7f]/.test(value);
|
|
12
|
+
const requireThat = (condition, label) => { if (!condition) throw new HarnessError('Invalid agent configuration: ' + label); };
|
|
13
|
+
function fields(value, allowed, required, label) {
|
|
14
|
+
requireThat(isObject(value) && Object.keys(value).every(key => allowed.includes(key))
|
|
15
|
+
&& required.every(key => Object.hasOwn(value, key)), label + ' fields');
|
|
16
|
+
}
|
|
17
|
+
function reference(value, label) {
|
|
18
|
+
requireThat(text(value), label + ' must be nonempty single-line text (max 256 characters)');
|
|
19
|
+
if (value.startsWith('@')) requireThat(SLUG.test(value.slice(1)), label + ' alias syntax');
|
|
20
|
+
else requireThat(!/\s/.test(value), label + ' model ID must not contain whitespace');
|
|
21
|
+
}
|
|
22
|
+
function provider(value, label) { requireThat(typeof value === 'string' && SLUG.test(value), label + ' provider ID'); }
|
|
23
|
+
function reasoning(value, label) { requireThat(REASONING.includes(value), label + ' reasoning level'); }
|
|
24
|
+
|
|
25
|
+
export function resolveModel(config, referenceValue, visited = []) {
|
|
26
|
+
if (!referenceValue.startsWith('@')) return { model: referenceValue, provider: config.provider };
|
|
27
|
+
const alias = referenceValue.slice(1);
|
|
28
|
+
requireThat(Object.hasOwn(config.models, alias), 'unknown alias @' + alias);
|
|
29
|
+
requireThat(!visited.includes(alias), 'cyclic alias @' + alias);
|
|
30
|
+
const descriptor = config.models[alias];
|
|
31
|
+
const next = typeof descriptor === 'string' ? descriptor : descriptor.model;
|
|
32
|
+
const resolved = resolveModel(config, next, [...visited, alias]);
|
|
33
|
+
return { ...resolved, provider: typeof descriptor === 'object' ? descriptor.provider ?? resolved.provider : resolved.provider };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function validateAgentConfig(config) {
|
|
37
|
+
fields(config, ['schema_version', 'provider', 'defaultModel', 'defaultReasoning', 'models', 'agents', 'execution', 'hosts'],
|
|
38
|
+
['schema_version', 'provider', 'defaultModel', 'defaultReasoning', 'models', 'agents', 'execution'], 'root');
|
|
39
|
+
requireThat(config.schema_version === 1, 'unsupported schema_version');
|
|
40
|
+
provider(config.provider, 'default'); reference(config.defaultModel, 'defaultModel');
|
|
41
|
+
reasoning(config.defaultReasoning, 'default');
|
|
42
|
+
requireThat(isObject(config.models) && Object.keys(config.models).length <= 64, 'models map (max 64 aliases)');
|
|
43
|
+
for (const [alias, descriptor] of Object.entries(config.models)) {
|
|
44
|
+
requireThat(SLUG.test(alias), 'alias ID');
|
|
45
|
+
if (typeof descriptor === 'string') reference(descriptor, 'models.' + alias);
|
|
46
|
+
else {
|
|
47
|
+
fields(descriptor, ['model', 'provider'], ['model'], 'models.' + alias);
|
|
48
|
+
reference(descriptor.model, 'models.' + alias);
|
|
49
|
+
if (descriptor.provider !== undefined) provider(descriptor.provider, 'models.' + alias);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
resolveModel(config, config.defaultModel);
|
|
53
|
+
for (const alias of Object.keys(config.models)) resolveModel(config, '@' + alias);
|
|
54
|
+
requireThat(isObject(config.agents) && Object.keys(config.agents).length <= 128, 'agents map (max 128 roles)');
|
|
55
|
+
for (const [id, agent] of Object.entries(config.agents)) {
|
|
56
|
+
requireThat(ROLE.test(id), 'role ID must use namespace:role');
|
|
57
|
+
fields(agent, ['model', 'provider', 'reasoning', 'enabled'], [], id);
|
|
58
|
+
if (agent.model !== undefined) { reference(agent.model, id); resolveModel(config, agent.model); }
|
|
59
|
+
if (agent.provider !== undefined) provider(agent.provider, id);
|
|
60
|
+
if (agent.reasoning !== undefined) reasoning(agent.reasoning, id);
|
|
61
|
+
requireThat(agent.enabled === undefined || typeof agent.enabled === 'boolean', id + ' enabled');
|
|
62
|
+
}
|
|
63
|
+
const execution = config.execution;
|
|
64
|
+
fields(execution, ['mode', 'maxConcurrentAgents', 'maxRounds', 'autoEscalateToPremium'],
|
|
65
|
+
['mode', 'maxConcurrentAgents', 'maxRounds', 'autoEscalateToPremium'], 'execution');
|
|
66
|
+
requireThat(['single-agent', 'hybrid', 'multi-agent'].includes(execution.mode), 'execution mode');
|
|
67
|
+
requireThat(Number.isSafeInteger(execution.maxConcurrentAgents) && execution.maxConcurrentAgents >= 1
|
|
68
|
+
&& execution.maxConcurrentAgents <= 32, 'maxConcurrentAgents must be an integer from 1 to 32');
|
|
69
|
+
requireThat(execution.mode !== 'single-agent' || execution.maxConcurrentAgents === 1, 'single-agent requires maxConcurrentAgents=1');
|
|
70
|
+
requireThat(Number.isSafeInteger(execution.maxRounds) && execution.maxRounds >= 1 && execution.maxRounds <= 20,
|
|
71
|
+
'maxRounds must be an integer from 1 to 20');
|
|
72
|
+
requireThat(execution.autoEscalateToPremium === false, 'autoEscalateToPremium must be false; select premium explicitly');
|
|
73
|
+
const hosts = config.hosts ?? {};
|
|
74
|
+
requireThat(isObject(hosts) && Object.keys(hosts).length <= 16, 'hosts map (max 16 profiles)');
|
|
75
|
+
for (const [id, profile] of Object.entries(hosts)) {
|
|
76
|
+
requireThat(SLUG.test(id), 'host ID'); fields(profile, ['models', 'reasoning'], [], 'host ' + id);
|
|
77
|
+
for (const key of ['models', 'reasoning']) {
|
|
78
|
+
const map = profile[key] ?? {};
|
|
79
|
+
requireThat(isObject(map) && Object.keys(map).length <= 128, 'host ' + id + ' ' + key + ' map');
|
|
80
|
+
for (const [from, to] of Object.entries(map)) {
|
|
81
|
+
requireThat(key === 'models' ? text(from) && from.includes('/') : REASONING.includes(from), 'host mapping key');
|
|
82
|
+
requireThat(text(to) && !to.startsWith('@'), 'host mapping target must be explicit native text');
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return config;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function readConfiguration(root, relative) {
|
|
90
|
+
const filename = safe(root, relative), info = stat(filename);
|
|
91
|
+
if (!info) return null;
|
|
92
|
+
requireThat(info.isFile() && info.size <= 1024 * 1024, relative + ' must be a file up to 1 MiB');
|
|
93
|
+
let config;
|
|
94
|
+
try { config = JSON.parse(fs.readFileSync(filename, 'utf8')); }
|
|
95
|
+
catch { throw new HarnessError('Invalid JSON in ' + relative); }
|
|
96
|
+
return validateAgentConfig(config);
|
|
97
|
+
}
|
|
98
|
+
export function loadAgentConfig(root) { return readConfiguration(root, AGENT_CONFIG); }
|
|
99
|
+
|
|
100
|
+
export function agentDefaults(source = SOURCE, preset = 'recommended') {
|
|
101
|
+
requireThat(PRESETS.includes(preset), 'unknown preset; use recommended, economy or custom');
|
|
102
|
+
// Older alternate bundles remain usable; the packaged CLI supplies the new canonical seed.
|
|
103
|
+
const config = structuredClone(readConfiguration(source, DEFAULT_AGENTS) ?? readConfiguration(SOURCE, DEFAULT_AGENTS));
|
|
104
|
+
requireThat(config !== null, 'missing canonical default');
|
|
105
|
+
if (preset === 'economy') {
|
|
106
|
+
config.defaultModel = '@economy'; config.defaultReasoning = 'low';
|
|
107
|
+
config.execution.mode = 'single-agent'; config.execution.maxConcurrentAgents = 1;
|
|
108
|
+
for (const agent of Object.values(config.agents)) { agent.model = '@economy'; agent.reasoning = 'low'; }
|
|
109
|
+
}
|
|
110
|
+
return validateAgentConfig(config);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function registeredRoles(source, capabilities = []) {
|
|
114
|
+
const roles = {};
|
|
115
|
+
const folder = path.dirname(safe(source, 'core/agents/.role-index'));
|
|
116
|
+
for (const name of fs.readdirSync(folder).filter(name => name.endsWith('.md')).sort()) {
|
|
117
|
+
roles['core:' + name.slice(0, -3)] = 'core/agents/' + name;
|
|
118
|
+
}
|
|
119
|
+
for (const id of capabilities) {
|
|
120
|
+
const metadata = readJson(safe(source, `capabilities/${id}/capability.json`));
|
|
121
|
+
for (const file of metadata.agents ?? []) {
|
|
122
|
+
const role = id + ':' + path.basename(file, '.md');
|
|
123
|
+
roles[role] = `capabilities/${id}/${file}`;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return roles;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function resolveAgent(config, id) {
|
|
130
|
+
const agent = Object.hasOwn(config.agents, id) ? config.agents[id] : {};
|
|
131
|
+
const resolved = resolveModel(config, agent.model ?? config.defaultModel);
|
|
132
|
+
return { id, ...resolved, provider: agent.provider ?? resolved.provider,
|
|
133
|
+
reasoning: agent.reasoning ?? config.defaultReasoning, enabled: agent.enabled !== false,
|
|
134
|
+
source: Object.hasOwn(config.agents, id) ? 'role' : 'project-default' };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function agentPlan(root, host) {
|
|
138
|
+
if (host !== undefined) requireThat(typeof host === 'string' && SLUG.test(host), 'host ID');
|
|
139
|
+
const config = loadAgentConfig(root);
|
|
140
|
+
const presets = [{ label: 'A', value: 'recommended', description: 'Sol, hybrid, at most two concurrent agents when supported.' },
|
|
141
|
+
{ label: 'B', value: 'economy', description: 'Economy model, single-agent roles.' },
|
|
142
|
+
{ label: 'C', value: 'custom', description: 'Edit provider/model/reasoning and role/host mappings explicitly.' }];
|
|
143
|
+
const note = 'Read-only declaration. No provider/model probing, native config writes, agent spawning, concurrency/token enforcement or usage tracking. Verify model access, effort and delegation in the actual host before execution.';
|
|
144
|
+
if (!config) return { status: 'not-configured', configured: false, presets, roles: [], note };
|
|
145
|
+
const lock = readJson(safe(root, '.harness/lock.json'));
|
|
146
|
+
const registry = registeredRoles(path.join(root, '.harness'), lock.capabilities ?? []);
|
|
147
|
+
const ids = [...new Set([...Object.keys(registry), ...Object.keys(config.agents)])].sort();
|
|
148
|
+
const profile = config.hosts?.[host] ?? {};
|
|
149
|
+
const roles = ids.map(id => {
|
|
150
|
+
const requested = resolveAgent(config, id);
|
|
151
|
+
const active = requested.enabled && Object.hasOwn(registry, id);
|
|
152
|
+
const mapping = profile.models?.[requested.provider + '/' + requested.model];
|
|
153
|
+
const incompatible = !mapping && ((host === 'claude-code' &&
|
|
154
|
+
(!['anthropic', 'bedrock', 'vertex'].includes(requested.provider) || requested.model.startsWith('gpt-'))) ||
|
|
155
|
+
(host === 'gemini-cli' && (!['google', 'vertex'].includes(requested.provider) || requested.model.startsWith('gpt-'))));
|
|
156
|
+
return { ...requested, active, contract: registry[id] ? '.harness/' + registry[id] : null,
|
|
157
|
+
host_model: mapping ?? requested.model, host_reasoning: profile.reasoning?.[requested.reasoning] ?? requested.reasoning,
|
|
158
|
+
mapping: mapping ? 'explicit-user-host-mapping' : 'requested',
|
|
159
|
+
availability: active && incompatible ? 'incompatible-provider' : 'unverified',
|
|
160
|
+
runtime_verified: false };
|
|
161
|
+
});
|
|
162
|
+
return { status: roles.some(role => role.active && role.availability === 'incompatible-provider') ? 'host-incompatible' : 'requires-host-verification',
|
|
163
|
+
configured: true, host: host ?? null, presets, execution: config.execution, roles,
|
|
164
|
+
missing_mappings: Object.keys(registry).filter(id => !Object.hasOwn(config.agents, id)), note };
|
|
165
|
+
}
|
package/cli/environment.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { safe, stat, isObject, SLUG, HarnessError } from './files.js';
|
|
4
|
+
import { agentPlan } from './agents.js';
|
|
4
5
|
|
|
5
6
|
export const ENVIRONMENT = '.harness/environment.json';
|
|
6
7
|
export const MACHINE = '.harness/machine.json';
|
|
@@ -119,7 +120,7 @@ export function environmentReport(root, env = process.env) {
|
|
|
119
120
|
note: 'Presence/reference checks only. No commands executed; values and arguments are omitted. Never store credentials here.' };
|
|
120
121
|
}
|
|
121
122
|
|
|
122
|
-
export function setupPlan(root, adapters, env = process.env) {
|
|
123
|
+
export function setupPlan(root, adapters, env = process.env, host) {
|
|
123
124
|
const environment = environmentReport(root, env);
|
|
124
125
|
const configuredTools = environment.entries.some(e => e.group === 'tools');
|
|
125
126
|
const questions = [];
|
|
@@ -147,10 +148,22 @@ export function setupPlan(root, adapters, env = process.env) {
|
|
|
147
148
|
{ label: 'C', value: 'idea', description: 'Start from the user idea and clarify the scope.' }],
|
|
148
149
|
recommendation: null, reason: 'The host must inspect existing local.md and sources before recommending.',
|
|
149
150
|
allow_free_text: true
|
|
151
|
+
}, {
|
|
152
|
+
id: 'project-mode', question: 'What is the scope of the affected code?',
|
|
153
|
+
options: [{ label: 'A', value: 'maintain', description: 'Extend existing modules using their conventions and shared APIs.' },
|
|
154
|
+
{ label: 'B', value: 'new', description: 'Start a new application with a minimal agreed adapter baseline.' },
|
|
155
|
+
{ label: 'C', value: 'mixed', description: 'Add a new module while preserving repository-wide contracts.' }],
|
|
156
|
+
recommendation: null,
|
|
157
|
+
reason: 'Inspect the task, local.md and neighboring code; stack detection or environment configuration does not establish project mode. Skip if already known.',
|
|
158
|
+
allow_free_text: true
|
|
150
159
|
});
|
|
151
160
|
return { status: environment.problems.length ? 'blocked' : 'planned', environment, questions,
|
|
161
|
+
agent_configuration: agentPlan(root, host),
|
|
152
162
|
next_steps: ['Read AGENTS.md, .harness/local.md and the installed project-setup workflow.',
|
|
163
|
+
'Apply installed Core project-conventions.md and relevant adapter conventions; establish mode per affected module even when no setup questions are suggested.',
|
|
164
|
+
'Inspect comparable code and search public shared APIs before creating files, dependencies or abstractions; record source paths and reuse/new-code rationale.',
|
|
153
165
|
'Skip answered questions; ask at most 1–3 consequential questions with evidence-based recommendations.',
|
|
166
|
+
'Review agents.json provider/model/reasoning and preset choices; verify access/effort/delegation in the actual host before execution. No automatic premium fallback.',
|
|
154
167
|
'Record shared decisions in local.md/environment.json; put only machine paths in ignored machine.json.',
|
|
155
168
|
'Run aih doctor, then use the reviewed project commands through your AI host.'],
|
|
156
169
|
note: 'Read-only host-facing suggestions, not an interactive terminal wizard or approval.' };
|
package/cli/harness.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import { emptyEnvironment, environmentReport } from './environment.js';
|
|
3
|
+
import { AGENT_CONFIG, agentDefaults, loadAgentConfig } from './agents.js';
|
|
3
4
|
import path from 'node:path';
|
|
4
5
|
import { randomUUID } from 'node:crypto';
|
|
5
6
|
import { loadAdapter } from './adapter-schema.js';
|
|
@@ -66,6 +67,7 @@ export function bundle(source, config) {
|
|
|
66
67
|
files[dest] = fs.readFileSync(safe(source, `adapters/${a}/skills/${name}/SKILL.md`));
|
|
67
68
|
}
|
|
68
69
|
}
|
|
70
|
+
files['.harness/templates/agents.default.json'] = encode(agentDefaults(source));
|
|
69
71
|
for (const c of capabilities) {
|
|
70
72
|
const info = loadCapability(source, c);
|
|
71
73
|
for (const name of info.skills) {
|
|
@@ -157,12 +159,15 @@ export function transaction(root, changes, writer = atomicWrite) {
|
|
|
157
159
|
}
|
|
158
160
|
}
|
|
159
161
|
|
|
160
|
-
export function install(root, source, { command = 'init', adapters, capabilities, dryRun = false, allowMajor = false, writer } = {}) {
|
|
162
|
+
export function install(root, source, { command = 'init', adapters, capabilities, agentPreset, dryRun = false, allowMajor = false, writer } = {}) {
|
|
161
163
|
assertSeparate(root, source);
|
|
162
164
|
return guard(root, () => {
|
|
163
165
|
const hasLock = Boolean(stat(safe(root, LOCK)));
|
|
164
166
|
if (command === 'update' && !hasLock) throw new HarnessError('Not installed; run init first');
|
|
165
167
|
const old = hasLock ? loadLock(root) : null;
|
|
168
|
+
const existingAgents = loadAgentConfig(root);
|
|
169
|
+
if (agentPreset !== undefined && command !== 'init') throw new HarnessError('Agent preset is supported only by init');
|
|
170
|
+
if (agentPreset !== undefined && existingAgents) throw new HarnessError('agents.json is project-owned; edit it explicitly instead of applying an init preset');
|
|
166
171
|
const config = projectConfig(root, adapters, capabilities, hasLock);
|
|
167
172
|
const { files, lock, warnings } = bundle(source, config);
|
|
168
173
|
if (old) {
|
|
@@ -186,10 +191,11 @@ export function install(root, source, { command = 'init', adapters, capabilities
|
|
|
186
191
|
}
|
|
187
192
|
if (old) for (const name of Object.keys(old.files)) if (!Object.hasOwn(files, name)) changes[name] = null;
|
|
188
193
|
const seeds = {
|
|
194
|
+
[AGENT_CONFIG]: encode(agentDefaults(source, agentPreset ?? 'recommended')),
|
|
189
195
|
[CONFIG]: encode(config),
|
|
190
196
|
'.harness/environment.json': encode(emptyEnvironment()),
|
|
191
197
|
'openspec/config.yaml': fs.readFileSync(safe(source, 'templates/openspec/config.yaml')),
|
|
192
|
-
'.harness/local.md': Buffer.from('# Project overrides\n\nRecord project facts and reviewed exceptions here. Never weaken security boundaries.\n'),
|
|
198
|
+
'.harness/local.md': Buffer.from('# Project overrides\n\nRecord project facts and reviewed exceptions here. Never weaken security boundaries.\n\n## Development conventions\nRecord scope/mode (new, maintain or mixed), authoritative source paths, module boundaries, reusable public APIs, naming/error/test conventions and configured checks. Link existing guides instead of duplicating them. Unknowns are not approved defaults; the AI host inspects evidence and asks only consequential questions.\n'),
|
|
193
199
|
};
|
|
194
200
|
for (const [name, data] of Object.entries(seeds)) if (!stat(safe(root, name))) changes[name] = data;
|
|
195
201
|
// Project-owned ignore file: preserve all prior rules, place our anchored rule last.
|
|
@@ -210,6 +216,7 @@ export function install(root, source, { command = 'init', adapters, capabilities
|
|
|
210
216
|
export function check(root) {
|
|
211
217
|
const lock = loadLock(root), config = projectConfig(root, undefined, undefined, true);
|
|
212
218
|
const problems = drift(root, lock);
|
|
219
|
+
loadAgentConfig(root); // Optional on legacy installs; validate user config without managing its bytes.
|
|
213
220
|
if (JSON.stringify([...config.adapters].sort()) !== JSON.stringify([...lock.adapters].sort())) problems.push(`${CONFIG}: adapter selection changed; run update`);
|
|
214
221
|
if (JSON.stringify([...(config.capabilities ?? [])].sort()) !== JSON.stringify([...(lock.capabilities ?? [])].sort())) problems.push(`${CONFIG}: capability selection changed; run update`);
|
|
215
222
|
return { status: problems.length ? 'error' : 'ok', version: lock.version, problems };
|
|
@@ -237,20 +244,28 @@ export function newChange(root, name) {
|
|
|
237
244
|
});
|
|
238
245
|
}
|
|
239
246
|
|
|
240
|
-
//
|
|
241
|
-
export function newDoc(root, name) {
|
|
247
|
+
// Capability drafts are project-owned and never added to the managed bundle.
|
|
248
|
+
export function newDoc(root, name, capability = 'product') {
|
|
242
249
|
if (!SLUG.test(name)) throw new HarnessError('Document type must be a lowercase hyphenated slug');
|
|
250
|
+
if (!SLUG.test(capability)) throw new HarnessError('Capability must be a lowercase hyphenated slug');
|
|
243
251
|
return guard(root, () => {
|
|
244
252
|
if (check(root).status !== 'ok') throw new HarnessError('Fix installation drift before creating a document');
|
|
245
253
|
const lock = loadLock(root);
|
|
246
|
-
if (!(lock.capabilities ?? []).includes(
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
const
|
|
254
|
+
if (!(lock.capabilities ?? []).includes(capability)) {
|
|
255
|
+
throw new HarnessError(capability === 'product' ? 'Product capability is not installed' : `Capability is not installed: ${capability}`);
|
|
256
|
+
}
|
|
257
|
+
const sourceName = `.harness/capabilities/${capability}/templates/${name}.md`;
|
|
258
|
+
if (!Object.hasOwn(lock.files, `.harness/capabilities/${capability}/capability.json`)) {
|
|
259
|
+
throw new HarnessError(`Capability metadata is not locked: ${capability}`);
|
|
260
|
+
}
|
|
261
|
+
const declared = loadCapability(path.join(root, '.harness'), capability).templates;
|
|
262
|
+
if (!declared.includes(`templates/${name}.md`)) throw new HarnessError(`Unknown document type for ${capability}: ${name}`);
|
|
263
|
+
if (!Object.hasOwn(lock.files, sourceName)) throw new HarnessError(`Document template is not locked: ${capability}/${name}`);
|
|
264
|
+
const folder = capability === 'product' && ['idea', 'research'].includes(name) ? 'discovery' : capability;
|
|
250
265
|
const outputName = `docs/${folder}/${name}.md`;
|
|
251
266
|
if (stat(safe(root, outputName))) throw new HarnessError(`Document already exists: ${outputName}`);
|
|
252
267
|
transaction(root, { [outputName]: fs.readFileSync(safe(root, sourceName)) });
|
|
253
|
-
return { status: 'ok', type: name, path: outputName,
|
|
268
|
+
return { status: 'ok', type: name, capability, path: outputName,
|
|
254
269
|
note: 'Template only; populate from authorized evidence and obtain human approval.' };
|
|
255
270
|
});
|
|
256
271
|
}
|
package/cli/main.js
CHANGED
|
@@ -24,16 +24,19 @@ Usage: aih <command> [options]
|
|
|
24
24
|
setup Suggest first-run questions for the AI host (read-only)
|
|
25
25
|
ready <id> Check structural change readiness, not approval
|
|
26
26
|
new-change <id> Create an OpenSpec draft using installed templates
|
|
27
|
-
new-doc <type> Scaffold a
|
|
27
|
+
new-doc <type> Scaffold a locked capability template (default: product)
|
|
28
28
|
|
|
29
29
|
Options:
|
|
30
30
|
--target <directory> Project directory (default: current directory)
|
|
31
31
|
--detect init only: opt in to detected stack selection
|
|
32
32
|
--stage <type> ready only: implement (default) or review
|
|
33
|
+
--capability <id> new-doc only: installed template owner (default: product)
|
|
34
|
+
--agent-preset <id> init only: recommended (default), economy, custom; no overwrite
|
|
35
|
+
--host <id> setup only: codex,claude-code,gemini-cli,cursor,copilot or custom host ID
|
|
33
36
|
--scope <type> verify/eval: adapters (default), capabilities, core, all
|
|
34
37
|
--report <file> eval only: score a reviewer-completed JSON report
|
|
35
38
|
--adapters <a,b> init/resolve: java-spring,react-nextjs,react-native,ai-python,kubernetes,astro
|
|
36
|
-
--capabilities <a,b> init/resolve: uiux,product,devops,monetization
|
|
39
|
+
--capabilities <a,b> init/resolve: uiux,product,architecture,delivery,devops,monetization
|
|
37
40
|
--source <directory> init/update/list/detect/resolve: trusted alternate bundle directory
|
|
38
41
|
--dry-run init/update: preview without persistent changes
|
|
39
42
|
--allow-major init/update: acknowledge a major version migration
|
|
@@ -51,6 +54,8 @@ Examples:
|
|
|
51
54
|
aih init --adapters astro --capabilities monetization
|
|
52
55
|
aih resolve --adapters java-spring,kubernetes --capabilities product,uiux,devops
|
|
53
56
|
aih setup
|
|
57
|
+
aih init --agent-preset economy
|
|
58
|
+
aih setup --host codex
|
|
54
59
|
aih doctor
|
|
55
60
|
aih ready add-order --stage implement
|
|
56
61
|
aih skills
|
|
@@ -60,6 +65,9 @@ Examples:
|
|
|
60
65
|
aih new-doc prd-lite
|
|
61
66
|
aih new-doc srs
|
|
62
67
|
aih new-doc use-cases
|
|
68
|
+
aih new-doc mvp
|
|
69
|
+
aih new-doc overview --capability architecture
|
|
70
|
+
aih new-doc delivery-plan --capability delivery
|
|
63
71
|
aih init --adapters java-spring,kubernetes --capabilities product,devops
|
|
64
72
|
aih skill harness-product-brd-generation
|
|
65
73
|
aih skill harness-uiux-figma-design
|
|
@@ -71,7 +79,8 @@ export function main(argv = process.argv.slice(2)) {
|
|
|
71
79
|
let args;
|
|
72
80
|
try {
|
|
73
81
|
args = parseArgs({ args: argv, allowPositionals: true, strict: true, tokens: true, options: {
|
|
74
|
-
target: { type: 'string' }, adapters: { type: 'string' }, capabilities: { type: 'string' }, source: { type: 'string' },
|
|
82
|
+
target: { type: 'string' }, adapters: { type: 'string' }, capabilities: { type: 'string' }, capability: { type: 'string' }, source: { type: 'string' },
|
|
83
|
+
'agent-preset': { type: 'string' }, host: { type: 'string' },
|
|
75
84
|
detect: { type: 'boolean' }, report: { type: 'string' }, scope: { type: 'string' }, stage: { type: 'string' },
|
|
76
85
|
'dry-run': { type: 'boolean' }, 'allow-major': { type: 'boolean' },
|
|
77
86
|
help: { type: 'boolean', short: 'h' }, version: { type: 'boolean', short: 'v' },
|
|
@@ -87,9 +96,12 @@ export function main(argv = process.argv.slice(2)) {
|
|
|
87
96
|
if (!['init', 'update', 'check', 'doctor', 'new-change', 'new-doc', 'list', 'detect', 'resolve', 'verify', 'eval', 'skills', 'skill', 'setup', 'ready'].includes(command)) throw new Error(`Unknown command: ${command}`);
|
|
88
97
|
if (extra.length || (!['new-change', 'new-doc', 'skill', 'ready'].includes(command) && name !== undefined) || (['new-change', 'new-doc', 'skill', 'ready'].includes(command) && name === undefined)) throw new Error('Unexpected or missing positional argument; run aih --help');
|
|
89
98
|
const allowed = new Set(['target', ...(['init', 'update'].includes(command) ? ['source', 'dry-run', 'allow-major'] : []), ...(['init', 'resolve'].includes(command) ? ['adapters', 'capabilities'] : []), ...(command === 'init' ? ['detect'] : []), ...(['list', 'detect', 'resolve'].includes(command) ? ['source'] : []), ...(command === 'eval' ? ['report'] : []), ...(command === 'ready' ? ['stage'] : []), ...(['verify', 'eval'].includes(command) ? ['scope'] : [])]);
|
|
99
|
+
if (command === 'new-doc') allowed.add('capability');
|
|
100
|
+
if (command === 'init') allowed.add('agent-preset');
|
|
101
|
+
if (command === 'setup') allowed.add('host');
|
|
90
102
|
for (const key of Object.keys(args.values)) if (!allowed.has(key)) throw new Error(`--${key} is not supported for ${command}`);
|
|
91
103
|
if (args.values.detect && args.values.adapters !== undefined) throw new Error('Choose either --detect or --adapters');
|
|
92
|
-
for (const key of ['target', 'source', 'adapters', 'capabilities', 'report']) if (args.values[key] !== undefined && !args.values[key].trim()) throw new Error(`--${key} cannot be empty`);
|
|
104
|
+
for (const key of ['target', 'source', 'adapters', 'capabilities', 'capability', 'report', 'agent-preset', 'host']) if (args.values[key] !== undefined && !args.values[key].trim()) throw new Error(`--${key} cannot be empty`);
|
|
93
105
|
} catch (error) { console.error(JSON.stringify({ status: 'error', message: error.message })); return 2; }
|
|
94
106
|
try {
|
|
95
107
|
const { values, positionals: [command, name] } = args;
|
|
@@ -101,6 +113,7 @@ export function main(argv = process.argv.slice(2)) {
|
|
|
101
113
|
if (values.detect && !selection.length) throw new Error('No stack detected; choose --adapters explicitly or omit --detect for core only');
|
|
102
114
|
result = install(root, source, {
|
|
103
115
|
command, adapters: selection ?? (values.adapters === undefined ? undefined : values.adapters.split(',').map(v => v.trim())),
|
|
116
|
+
agentPreset: values['agent-preset'],
|
|
104
117
|
capabilities: values.capabilities === undefined ? undefined : values.capabilities.split(',').map(v => v.trim()),
|
|
105
118
|
dryRun: values['dry-run'], allowMajor: values['allow-major'],
|
|
106
119
|
});
|
|
@@ -121,10 +134,10 @@ export function main(argv = process.argv.slice(2)) {
|
|
|
121
134
|
else if (command === 'setup' || command === 'ready') {
|
|
122
135
|
const state = check(root);
|
|
123
136
|
if (state.status !== 'ok') throw new Error('Fix installation drift before setup/readiness checks');
|
|
124
|
-
result = command === 'setup' ? setupPlan(root, loadLock(root).adapters) : ready(root, name, values.stage ?? 'implement');
|
|
137
|
+
result = command === 'setup' ? setupPlan(root, loadLock(root).adapters, process.env, values.host) : ready(root, name, values.stage ?? 'implement');
|
|
125
138
|
}
|
|
126
139
|
else if (command === 'new-change') result = newChange(root, name);
|
|
127
|
-
else if (command === 'new-doc') result = newDoc(root, name);
|
|
140
|
+
else if (command === 'new-doc') result = newDoc(root, name, values.capability ?? 'product');
|
|
128
141
|
else result = command === 'doctor' ? doctor(root) : check(root);
|
|
129
142
|
if (command === 'init' || command === 'update') result.next_steps = values['dry-run']
|
|
130
143
|
? ['Review the proposed changes, then repeat without --dry-run.']
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": 1,
|
|
3
|
+
"provider": "openai",
|
|
4
|
+
"defaultModel": "gpt-6.1-sol",
|
|
5
|
+
"defaultReasoning": "medium",
|
|
6
|
+
"models": {
|
|
7
|
+
"economy": "gpt-6-luna",
|
|
8
|
+
"balanced": "gpt-6.1-sol",
|
|
9
|
+
"premium": "gpt-6-astra"
|
|
10
|
+
},
|
|
11
|
+
"agents": {
|
|
12
|
+
"core:implementer": {
|
|
13
|
+
"model": "@balanced",
|
|
14
|
+
"reasoning": "medium"
|
|
15
|
+
},
|
|
16
|
+
"core:planner": {
|
|
17
|
+
"model": "@balanced",
|
|
18
|
+
"reasoning": "high"
|
|
19
|
+
},
|
|
20
|
+
"core:reviewer": {
|
|
21
|
+
"model": "@balanced",
|
|
22
|
+
"reasoning": "high"
|
|
23
|
+
},
|
|
24
|
+
"core:security-reviewer": {
|
|
25
|
+
"model": "@balanced",
|
|
26
|
+
"reasoning": "high"
|
|
27
|
+
},
|
|
28
|
+
"core:test-engineer": {
|
|
29
|
+
"model": "@balanced",
|
|
30
|
+
"reasoning": "medium"
|
|
31
|
+
},
|
|
32
|
+
"core:orchestrator": {
|
|
33
|
+
"model": "@balanced",
|
|
34
|
+
"reasoning": "high"
|
|
35
|
+
},
|
|
36
|
+
"core:documentation": {
|
|
37
|
+
"model": "@economy",
|
|
38
|
+
"reasoning": "low"
|
|
39
|
+
},
|
|
40
|
+
"product:business-analyst": {
|
|
41
|
+
"model": "@balanced",
|
|
42
|
+
"reasoning": "medium"
|
|
43
|
+
},
|
|
44
|
+
"product:product-manager": {
|
|
45
|
+
"model": "@balanced",
|
|
46
|
+
"reasoning": "medium"
|
|
47
|
+
},
|
|
48
|
+
"product:product-owner": {
|
|
49
|
+
"model": "@balanced",
|
|
50
|
+
"reasoning": "medium"
|
|
51
|
+
},
|
|
52
|
+
"product:project-manager": {
|
|
53
|
+
"model": "@balanced",
|
|
54
|
+
"reasoning": "medium"
|
|
55
|
+
},
|
|
56
|
+
"product:requirements-reviewer": {
|
|
57
|
+
"model": "@balanced",
|
|
58
|
+
"reasoning": "high"
|
|
59
|
+
},
|
|
60
|
+
"architecture:solution-architect": {
|
|
61
|
+
"model": "@balanced",
|
|
62
|
+
"reasoning": "high"
|
|
63
|
+
},
|
|
64
|
+
"architecture:architecture-reviewer": {
|
|
65
|
+
"model": "@balanced",
|
|
66
|
+
"reasoning": "high"
|
|
67
|
+
},
|
|
68
|
+
"delivery:project-manager": {
|
|
69
|
+
"model": "@balanced",
|
|
70
|
+
"reasoning": "medium"
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
"execution": {
|
|
74
|
+
"mode": "hybrid",
|
|
75
|
+
"maxConcurrentAgents": 2,
|
|
76
|
+
"maxRounds": 3,
|
|
77
|
+
"autoEscalateToPremium": false
|
|
78
|
+
},
|
|
79
|
+
"hosts": {}
|
|
80
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Documentation
|
|
2
|
+
|
|
3
|
+
Tool-neutral role contract; configuration is a preference, not a runtime.
|
|
4
|
+
## Inputs
|
|
5
|
+
Authorized task, current project facts, selected workflows, `.harness/agents.json` and actual host capability.
|
|
6
|
+
## Outputs
|
|
7
|
+
Source-linked document changes, preserved decision authority and actual verification limits.
|
|
8
|
+
## Permissions and limits
|
|
9
|
+
Follow Core permissions; never spawn workers, switch models or enforce token/round limits merely because
|
|
10
|
+
JSON requests it. Premium requires explicit user model selection. No approval, merge or publication by role name.
|
|
11
|
+
## Handoff
|
|
12
|
+
Use `.harness/core/workflows/agent-routing.md`. Pass only scope, source paths/revisions, decisions,
|
|
13
|
+
acceptance, actual checks, blockers and next owner. Keep simple tasks in one host session.
|
|
@@ -4,9 +4,12 @@ This is a tool-neutral role contract, not an autonomous process or a promise of
|
|
|
4
4
|
|
|
5
5
|
## Inputs
|
|
6
6
|
Agreed task, role handoff, adapter rules and tests.
|
|
7
|
+
Include project mode, convention sources and reuse plan from `.harness/core/rules/project-conventions.md`.
|
|
7
8
|
|
|
8
9
|
## Outputs
|
|
9
10
|
Produce a minimal diff, regression tests, executed check results and handoff to reviewer.
|
|
11
|
+
Include reused symbol/source paths, intended placement and rationale for new dependencies/components.
|
|
12
|
+
Preserve public boundaries; do not add parallel common helpers or unrelated abstractions.
|
|
10
13
|
|
|
11
14
|
## Permissions and limits
|
|
12
15
|
May edit within authorized scope and run bounded checks. No credentials, unapproved external costs or automatic merge.
|
|
@@ -14,3 +17,7 @@ May edit within authorized scope and run bounded checks. No credentials, unappro
|
|
|
14
17
|
## Handoff
|
|
15
18
|
State change ID, affected paths, decisions, evidence, outstanding questions and next owner.
|
|
16
19
|
Load the relevant skill and adapter. Respect the host's delegation policy; roles can run sequentially.
|
|
20
|
+
|
|
21
|
+
For new or consequential work, use `.harness/core/workflows/solution-framing.md` and relevant installed
|
|
22
|
+
Product/Architecture/Delivery workflows. Carry source revisions, MVP/AC/NFR/ADR IDs, accepted boundaries,
|
|
23
|
+
actual verification and unresolved dependencies in the handoff. Keep small clear fixes proportional.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Orchestrator
|
|
2
|
+
|
|
3
|
+
Tool-neutral role contract; configuration is a preference, not a runtime.
|
|
4
|
+
## Inputs
|
|
5
|
+
Authorized task, current project facts, selected workflows, `.harness/agents.json` and actual host capability.
|
|
6
|
+
## Outputs
|
|
7
|
+
Scoped plan, concise role handoffs and consolidated evidence/results.
|
|
8
|
+
## Permissions and limits
|
|
9
|
+
Follow Core permissions; never spawn workers, switch models or enforce token/round limits merely because
|
|
10
|
+
JSON requests it. Premium requires explicit user model selection. No approval, merge or publication by role name.
|
|
11
|
+
## Handoff
|
|
12
|
+
Use `.harness/core/workflows/agent-routing.md`. Pass only scope, source paths/revisions, decisions,
|
|
13
|
+
acceptance, actual checks, blockers and next owner. Keep simple tasks in one host session.
|
package/core/agents/planner.md
CHANGED
|
@@ -17,3 +17,7 @@ May read and draft specs. May not implement unrequested features, approve its ow
|
|
|
17
17
|
## Handoff
|
|
18
18
|
State change ID, affected paths, decisions, evidence, outstanding questions and next owner.
|
|
19
19
|
Load the relevant skill and adapter. Respect the host's delegation policy; roles can run sequentially.
|
|
20
|
+
|
|
21
|
+
For new or consequential work, use `.harness/core/workflows/solution-framing.md` and relevant installed
|
|
22
|
+
Product/Architecture/Delivery workflows. Carry source revisions, MVP/AC/NFR/ADR IDs, accepted boundaries,
|
|
23
|
+
actual verification and unresolved dependencies in the handoff. Keep small clear fixes proportional.
|
package/core/agents/reviewer.md
CHANGED
|
@@ -4,9 +4,13 @@ This is a tool-neutral role contract, not an autonomous process or a promise of
|
|
|
4
4
|
|
|
5
5
|
## Inputs
|
|
6
6
|
Base/head diff, proposal/specs, test logs and applicable standards.
|
|
7
|
+
Include convention sources and reuse evidence required by `.harness/core/rules/project-conventions.md`.
|
|
7
8
|
|
|
8
9
|
## Outputs
|
|
9
10
|
Produce prioritized findings with location, scenario, impact, remedy and remaining uncertainty.
|
|
11
|
+
Check placement, naming, public dependency direction, duplicate behavior, unsafe inherited patterns,
|
|
12
|
+
unnecessary abstractions/dependencies and unused code. Distinguish actual automated boundary results
|
|
13
|
+
from manual inspection; never claim `aih check` proves application architecture compliance.
|
|
10
14
|
|
|
11
15
|
## Permissions and limits
|
|
12
16
|
Review-only unless explicitly asked to fix findings. Never claim an independent review when the same model authored the code.
|
|
@@ -14,3 +18,7 @@ Review-only unless explicitly asked to fix findings. Never claim an independent
|
|
|
14
18
|
## Handoff
|
|
15
19
|
State change ID, affected paths, decisions, evidence, outstanding questions and next owner.
|
|
16
20
|
Load the relevant skill and adapter. Respect the host's delegation policy; roles can run sequentially.
|
|
21
|
+
|
|
22
|
+
For new or consequential work, use `.harness/core/workflows/solution-framing.md` and relevant installed
|
|
23
|
+
Product/Architecture/Delivery workflows. Carry source revisions, MVP/AC/NFR/ADR IDs, accepted boundaries,
|
|
24
|
+
actual verification and unresolved dependencies in the handoff. Keep small clear fixes proportional.
|