Skip to content

Commit 24fbdd4

Browse files
committed
fix(brief): tighten generated HTML guardrails
Require brief output to keep full source artifact coverage, label inferred impacts, avoid fallback claims from non-goals, and stay standalone with system fonts and project-neutral branding.
1 parent 4978ed0 commit 24fbdd4

9 files changed

Lines changed: 57 additions & 6 deletions

File tree

docs/commands.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,9 @@ Generate a one-page HTML brief that helps you review a change before implementat
341341

342342
**Important:**
343343
- The brief is not the source of truth; the original artifacts remain authoritative
344+
- The source artifact index includes every file path returned by the CLI context
344345
- Missing or ambiguous information is called out instead of invented
346+
- Generated HTML is standalone: no external fonts, CDN assets, network resources, unrelated branding, or agent-specific labels
345347
- Opening the file is best-effort; if it fails, the generated path is printed
346348

347349
**Example:**

docs/opsx.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,8 @@ Creates all planning artifacts at once. Use when you have a clear picture of wha
209209
```
210210
Reads the change artifacts and writes `brief.html` in the change directory. Use this optional workflow when you want a concise review surface before applying tasks. The original artifacts remain the source of truth.
211211

212+
The generated brief is standalone local HTML. It should not depend on remote fonts, CDN assets, network resources, unrelated branding, or agent-specific labels, and it should show source attribution for substantive claims.
213+
212214
### Implement (the fluid part)
213215
```
214216
/opsx:apply

openspec/changes/add-brief-workflow/design.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,18 @@ Alternative considered: add `openspec brief`. That would either be a weak determ
3434

3535
The requested review surface is browser-readable HTML. A second `brief.md` output would create synchronization questions without being needed for the MVP.
3636

37+
### Decision: Keep generated HTML self-contained and project-neutral
38+
39+
The workflow should instruct agents to use inline CSS and system fonts only. It should not pull Google Fonts, CDN assets, remote images, or any other network resources into `brief.html`.
40+
41+
The generated page should also avoid unrelated product names, project names, footers, and agent-specific implementation labels unless those details come from the source artifacts. This keeps the workflow portable across OpenSpec users and prevents local helper templates from leaking repo- or harness-specific branding into official output.
42+
3743
## Risks / Trade-offs
3844

3945
- [Risk] Agent-generated summaries can be inaccurate. -> Mitigation: require source attribution, require missing information to be called out, and state that original artifacts remain authoritative.
4046
- [Risk] `open` behavior is platform-specific. -> Mitigation: document best-effort opener commands and treat opener failure as non-fatal.
4147
- [Risk] Adding a workflow id can affect profile drift detection and generated command cleanup. -> Mitigation: update workflow maps and targeted tests around profile, generation, and tool detection.
48+
- [Risk] Local or agent-specific brief templates can leak unrelated branding or external assets. -> Mitigation: require standalone, project-neutral HTML and explicitly forbid remote fonts, CDN links, network assets, unrelated footers, and harness-specific labels unless sourced from artifacts.
4249

4350
## Migration Plan
4451

openspec/changes/add-brief-workflow/proposal.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ The workflow needs an optional agent-generated review surface that summarizes th
88

99
- Add an optional `/opsx:brief` workflow that produces a one-page HTML brief for an existing change.
1010
- The workflow reads the change status and apply context from the OpenSpec CLI, then reads the artifact files listed in `contextFiles`.
11-
- The agent synthesizes a concise human-readable brief with source attribution, risks, questions, implementation order, and verification plan.
11+
- The agent synthesizes a concise human-readable brief with source attribution, full source artifact coverage, risks, questions, implementation order, and verification plan.
12+
- The generated HTML is standalone and generic to the OpenSpec change: no external fonts, CDN links, network assets, unrelated branding, or agent-specific implementation labels.
1213
- The workflow writes `brief.html` inside the change directory and best-effort opens it with the host OS.
1314
- The workflow is selectable through the custom profile but is not included in the default `core` profile.
1415

openspec/changes/add-brief-workflow/specs/opsx-brief-skill/spec.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,15 @@ The brief workflow SHALL synthesize content from existing OpenSpec artifacts wit
3131
- **WHEN** the brief states a claim derived from an artifact
3232
- **THEN** the brief SHALL include source attribution to the relevant artifact path or artifact id
3333

34+
#### Scenario: Source artifact coverage
35+
- **WHEN** generating the source artifact index
36+
- **THEN** the brief SHALL include every file path returned in `contextFiles`
37+
- **AND** SHALL NOT silently omit delta spec files or schema-specific artifacts
38+
39+
#### Scenario: Inferred impacts
40+
- **WHEN** the brief includes affected modules, file areas, behavior changes, risks, or questions not explicitly stated by artifacts
41+
- **THEN** the brief SHALL label them as inference
42+
3443
### Requirement: HTML Brief Content
3544
The generated brief SHALL be concise, readable, and review-oriented.
3645

@@ -50,7 +59,13 @@ The generated brief SHALL be concise, readable, and review-oriented.
5059
#### Scenario: Static local HTML
5160
- **WHEN** writing `brief.html`
5261
- **THEN** the file SHALL be standalone static HTML
53-
- **AND** SHALL NOT depend on external JavaScript, CSS, fonts, or network resources
62+
- **AND** SHALL NOT depend on external JavaScript, CSS, fonts, CDN links, images, or network resources
63+
- **AND** SHALL use system fonts instead of remote fonts
64+
65+
#### Scenario: Generic OpenSpec output
66+
- **WHEN** writing `brief.html`
67+
- **THEN** the file SHALL NOT include unrelated product names, project names, footers, or branding unless present in the source artifacts
68+
- **AND** SHALL NOT include agent- or harness-specific paths or labels unless present in the source artifacts
5469

5570
### Requirement: Optional Workflow Installation
5671
The brief workflow SHALL be available for custom profile selection but not installed by the default core profile.

openspec/changes/add-brief-workflow/tasks.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
- [x] 1.3 Instruct agents to read `status --json`, `instructions apply --json`, and all `contextFiles`
66
- [x] 1.4 Require a static local `brief.html` output with source attribution and no external assets
77
- [x] 1.5 Include best-effort OS opener behavior with graceful fallback
8+
- [x] 1.6 Add fidelity guardrails for source artifact coverage, inferred impacts, task grouping, and project-neutral HTML output
89

910
## 2. Generation Integration
1011

src/core/templates/workflows/brief-change.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,12 @@ ${STORE_SELECTION_GUIDANCE}
5959
6060
Create a concise, human-readable review surface. The brief should help a user understand the change in about 3 minutes without reading every artifact.
6161
62+
Before writing HTML, build a short internal outline from the artifacts:
63+
- Source artifact index: every file path returned in \`contextFiles\`, grouped by artifact ID
64+
- Delta spec coverage: every spec file from \`contextFiles.specs\`
65+
- Task coverage: task groups aligned with the numbered headings in the tasks artifact; if tasks are summarized, say so
66+
- Impact list: affected modules or file areas stated by the artifacts; mark inferred impacts as "Inference"
67+
6268
Include these sections:
6369
- What this change does
6470
- Explicit non-goals
@@ -73,8 +79,11 @@ ${STORE_SELECTION_GUIDANCE}
7379
**Accuracy rules**
7480
- The original OpenSpec artifacts remain the source of truth. The brief is only a review surface.
7581
- Every substantive claim must include source attribution such as \`Source: proposal.md\`, \`Source: design.md\`, \`Source: specs/<capability>/spec.md\`, or \`Source: tasks.md\`.
82+
- The source artifact index must include every file path returned in \`contextFiles\`; do not silently drop delta specs or schema-specific artifacts.
7683
- If a section is not specified by the source artifacts, write "Not specified in source artifacts" instead of inventing content.
7784
- Separate direct source facts from your inference. Label inference as "Inference".
85+
- Do not turn non-goals into fallback plans or implied future work unless the source artifacts explicitly say so.
86+
- Do not add affected files, modules, user-visible behavior, risks, or questions unless they are sourced or clearly labeled as inference.
7887
- Keep the page concise. Prefer short bullets and compact tables over long prose.
7988
8089
6. **Write static HTML**
@@ -86,8 +95,11 @@ ${STORE_SELECTION_GUIDANCE}
8695
8796
The HTML must be:
8897
- standalone static HTML
89-
- no external JavaScript, CSS, fonts, images, or network resources
98+
- no external JavaScript, CSS, fonts, images, CDN links, or network resources
99+
- use system fonts only; do not import Google Fonts or other remote fonts
90100
- professional and readable in a browser
101+
- generic to the current OpenSpec change; do not include unrelated product names, project names, footers, or branding unless they appear in the source artifacts
102+
- agent-agnostic; do not hardcode tool-specific paths or labels such as \`.codex\`, \`.agents\`, Claude, Codex, or any harness-specific implementation detail unless the source artifacts explicitly mention them
91103
- safe for artifact text: escape \`&\`, \`<\`, \`>\`, \`"\`, and \`'\` if you include source excerpts or user-provided content
92104
93105
Use inline \`<style>\` only. Do not create additional files.

test/core/shared/skill-generation.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,17 @@ describe('skill-generation', () => {
5757
expect(uniqueIds.size).toBe(templates.length);
5858
});
5959

60+
it('should keep brief output standalone and project-neutral', () => {
61+
const brief = getSkillTemplates().find(t => t.workflowId === 'brief');
62+
63+
expect(brief?.template.instructions).toContain('contextFiles');
64+
expect(brief?.template.instructions).toContain('Source artifact index');
65+
expect(brief?.template.instructions).toContain('no external JavaScript, CSS, fonts, images, CDN links, or network resources');
66+
expect(brief?.template.instructions).toContain('use system fonts only');
67+
expect(brief?.template.instructions).toContain('do not include unrelated product names, project names, footers, or branding');
68+
expect(brief?.template.instructions).toContain('mark inferred impacts as "Inference"');
69+
});
70+
6071
it('should filter by workflow IDs when provided', () => {
6172
const filtered = getSkillTemplates(['propose', 'explore', 'apply', 'archive']);
6273
expect(filtered).toHaveLength(4);

test/core/templates/skill-templates-parity.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,15 @@ const EXPECTED_FUNCTION_HASHES: Record<string, string> = {
4141
getNewChangeSkillTemplate: '39663a6d2037e6697020393a66f6327506e3e3bc573b7a3556dcb7f9457dc51d',
4242
getContinueChangeSkillTemplate: '1bb28875d6e5946ea2ec5f12e90f55d9784c2fa1f6e4c4e2d0eda53d861d4c75',
4343
getApplyChangeSkillTemplate: '0f5a15fc7fb9ad6059a5643d0e01365d27642637a4aaebf182f9eabb45348197',
44-
getBriefChangeSkillTemplate: '02128880c929ff6d946a71ec41505a17c00bf251437133aaff4fdf0591644410',
44+
getBriefChangeSkillTemplate: 'ff06252ffdf94a86a4c9b2cd0c03a3374207b3ba929f42c1bec420e16bddaf03',
4545
getFfChangeSkillTemplate: '9f4c12a1c58c723c9c45a139307eb90caf39cedd93c435bc960d0817328875e2',
4646
getSyncSpecsSkillTemplate: '75abb20572256e2b8a647e77befae99f109ab5c4dc954a9c3c184829b5fcaa40',
4747
getOnboardSkillTemplate: 'e871d8ce172bb805ae62a7611aee7a3154d89414f427ad5ef31721c903f13002',
4848
getOpsxExploreCommandTemplate: '37e53590aae7ac6621d4393aa80a5b8af21881323887fa924ed329199fda27e0',
4949
getOpsxNewCommandTemplate: '57c600cce318d16b9b4308a18d0d983ea3c0673034e606a7cceec07b4c705e87',
5050
getOpsxContinueCommandTemplate: '418108b417107a87019d4020b26c105792d2ef0110fe6920445e255889216716',
5151
getOpsxApplyCommandTemplate: 'daeb507206707169de73c828e199648dde5732cbc17791ef2a027adffd028574',
52-
getOpsxBriefCommandTemplate: '51ba22fd5dc87650df50395cc655d69344b249f1b22dfb00f7aaca49b31b9563',
52+
getOpsxBriefCommandTemplate: 'c2884976627c6e9deabcf227b387671e1891d4b0ee66e50dd90b3a47a35fc3e3',
5353
getOpsxFfCommandTemplate: '36973ae0dd00ab169fbaaa42bf565f97e1bc97cf63ae7c07307734cc1ca8c1fd',
5454
getArchiveChangeSkillTemplate: 'c511a1c943bcfc5f9f3833b8c0ff284b22d34864a08f5f553cec471ee485d38f',
5555
getBulkArchiveChangeSkillTemplate: '0f635913757ae3d1609e111f4a8f699443ca47cbaaf8a1b21eb652f7b96a1d13',
@@ -69,7 +69,7 @@ const EXPECTED_GENERATED_SKILL_CONTENT_HASHES: Record<string, string> = {
6969
'openspec-new-change': 'bdb534d6d5a00b235f63852af089f904fd20df34be526ef67990ec3183829f33',
7070
'openspec-continue-change': '5d2aea621310d74d89e547d705d2e08e6d5a44da7bca93ba049ed43ebf60295e',
7171
'openspec-apply-change': '54cffa61274c6a499d2b3775e9f6db29255fd8e5ad99d7352c1e3bbe2edb45ed',
72-
'openspec-brief-change': 'f49222a1b4ff50eb53307b0f6cea511559066732d6f6f30e7a2698e6b2591731',
72+
'openspec-brief-change': 'a6268b84bd87f59e40e772e67043a1eca589b6d7e387f05bc74a99505ffebf1a',
7373
'openspec-ff-change': 'cbb7844c130bd188319ff2b3f0c0320243b5ae5b588a0f816cd4e29408f25676',
7474
'openspec-sync-specs': 'a81fd87f5e871874eab72e57c10a1949fde46d1d07d95f8ea3bc1a52b4e78c43',
7575
'openspec-archive-change': '833290ade47ddaed7f5e523d07437c7cef2497340021e944096bce449e290c22',

0 commit comments

Comments
 (0)