ace-handbook 0.31.1 → 0.32.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/README.md +1 -1
- data/handbook/skills/as-handbook-manage-overrides/SKILL.md +34 -0
- data/handbook/workflow-instructions/handbook/manage-cookbooks.wf.md +1 -0
- data/handbook/workflow-instructions/handbook/manage-overrides.wf.md +142 -0
- data/lib/ace/handbook/atoms/provider_registry.rb +4 -0
- data/lib/ace/handbook/cli/commands/sync.rb +11 -3
- data/lib/ace/handbook/models/prompt_template.rb +26 -0
- data/lib/ace/handbook/organisms/prompt_template_inventory.rb +85 -0
- data/lib/ace/handbook/organisms/provider_syncer.rb +65 -1
- data/lib/ace/handbook/version.rb +1 -1
- data/lib/ace/handbook.rb +2 -0
- metadata +6 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1e70a71fff6e61cbaa6724591eb965481b3b92153aed554c5fd6ad8b51f3e01e
|
|
4
|
+
data.tar.gz: 99db5b08f74300bfbc87b71f2621a0bcee70934d92a89d7a142884adb8a2e434
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 34c7119d2d01e4ad2ff58a6b91bc5b4b092fb42a575abc33319d3ee6f4c7e237058f1135c19ab560565ff3fa91a696e39a9b92e27b6b839f855de0bf20de0c64
|
|
7
|
+
data.tar.gz: c1b28858004960a5ef381e50bde467910b6694bfb29179e7cdd7014d45d25a2eb47ece9d75996cef92e35c30892705ca7687a6340f169b86e07781a287f8e499
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.32.0] - 2026-09-27
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Provider manifests may declare an optional `prompts_dir`; `ace-handbook sync` now projects canonical prompt templates from `ace-handbook-integration-*` packages (`handbook/prompts/*.md`) into that directory (idempotent writes; prunes only stale files carrying an `ace-handbook-integration-*` provenance marker, leaving user-authored templates untouched). Sync summary output gains a `; prompts <dir> (N prompts, U updated, R removed)` segment when a provider projects prompts.
|
|
14
|
+
- Shipped `wfi://handbook/manage-overrides` (with paired skill `as-handbook-manage-overrides`): canonical override documentation covering all overridable surfaces (workflows, cookbooks, guides), the source-priority resolution rule, skills' non-overridability ("override the workflow the skill calls"), the `.agents/` sync-projection status, and planted-competitor verification probes.
|
|
15
|
+
|
|
10
16
|
### Changed
|
|
11
17
|
- Tightened `wfi://release/rubygems-publish` baseline guidance with an OTP-burst publication contract (build all artifacts before OTP; push in ≤5 concurrent waves; defer metadata verification) for project overlays.
|
|
12
18
|
|
data/README.md
CHANGED
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
|
|
37
37
|
**Integrate with resource discovery and context loading** - pair with [ace-nav](../ace-support-nav) for workflow and resource discovery, [ace-bundle](../ace-bundle) for loading complete workflow instructions, and provider integrations (`ace-handbook-integration-*`) that can project canonical handbook skills into harness-native folders on request.
|
|
38
38
|
|
|
39
|
-
**Sync and inspect skill projections** - run `ace-handbook sync` to project canonical skills into `.agents/skills/` by default, or use `ace-handbook sync --provider PROVIDER` for harness-native folders such as `.codex/skills/`.
|
|
39
|
+
**Sync and inspect skill projections** - run `ace-handbook sync` to project canonical skills into `.agents/skills/` by default, or use `ace-handbook sync --provider PROVIDER` for harness-native folders such as `.codex/skills/`. Providers whose manifest also declares `prompts_dir` additionally receive canonical prompt templates from the matching integration package's `handbook/prompts/*.md` (projected flat into that directory; user-authored templates without an `ace-handbook-integration-*` provenance marker are never pruned).
|
|
40
40
|
|
|
41
41
|
**Extend handbook content in normal projects** - put project-specific workflows, guides, cookbooks, templates, and skills under `.ace-handbook/` and discover them with protocol URLs (`wfi://`, `guide://`, `cookbook://`, `tmpl://`, `skill://`). See [Usage Guide](docs/usage.md) for path conventions.
|
|
42
42
|
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: as-handbook-manage-overrides
|
|
3
|
+
description: Manage project-local overrides of ace-managed docs (workflows, cookbooks, guides) and verify resolution precedence
|
|
4
|
+
# bundle: wfi://handbook/manage-overrides
|
|
5
|
+
# context: no-fork
|
|
6
|
+
# agent: general-purpose
|
|
7
|
+
user-invocable: true
|
|
8
|
+
allowed-tools:
|
|
9
|
+
- Bash(ace-nav:*)
|
|
10
|
+
- Bash(ace-handbook:*)
|
|
11
|
+
- Bash(ace-bundle:*)
|
|
12
|
+
- Read
|
|
13
|
+
- Write
|
|
14
|
+
- Edit
|
|
15
|
+
- Glob
|
|
16
|
+
- LS
|
|
17
|
+
argument-hint: "[surface|uri] [action: plan|verify]"
|
|
18
|
+
last_modified: 2026-09-20
|
|
19
|
+
source: ace-handbook
|
|
20
|
+
integration:
|
|
21
|
+
targets:
|
|
22
|
+
- claude
|
|
23
|
+
- codex
|
|
24
|
+
- gemini
|
|
25
|
+
- opencode
|
|
26
|
+
- pi
|
|
27
|
+
providers: {}
|
|
28
|
+
skill:
|
|
29
|
+
kind: workflow
|
|
30
|
+
execution:
|
|
31
|
+
workflow: wfi://handbook/manage-overrides
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
Load and run `ace-bundle wfi://handbook/manage-overrides` in the current project, then follow the loaded workflow as the source of truth and execute it end-to-end instead of only summarizing it.
|
|
@@ -60,6 +60,7 @@ Brief description of what this cookbook accomplishes.
|
|
|
60
60
|
|
|
61
61
|
- Package-owned cookbooks (inside `ace-handbook`): `ace-handbook/handbook/cookbooks/`
|
|
62
62
|
- Project-local cookbooks (outside package scope): `.ace-handbook/cookbooks/`
|
|
63
|
+
- Project-local document overrides (workflows, cookbooks, guides) and resolution precedence are governed canonically by `wfi://handbook/manage-overrides`
|
|
63
64
|
|
|
64
65
|
## Process Steps
|
|
65
66
|
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
---
|
|
2
|
+
doc-type: workflow
|
|
3
|
+
name: manage-overrides
|
|
4
|
+
title: Manage Project-Local Overrides
|
|
5
|
+
description: Canonical reference for ace-managed document override surfaces, resolution precedence, and verification probes
|
|
6
|
+
purpose: Documentation for ace-handbook/handbook/workflow-instructions/handbook/manage-overrides.wf.md
|
|
7
|
+
allowed-tools:
|
|
8
|
+
- Bash(ace-nav:*)
|
|
9
|
+
- Bash(ace-bundle:*)
|
|
10
|
+
- Bash(ace-handbook:*)
|
|
11
|
+
- Read
|
|
12
|
+
- Write
|
|
13
|
+
- Edit
|
|
14
|
+
ace-docs:
|
|
15
|
+
last-updated: 2026-09-20
|
|
16
|
+
last-checked: 2026-09-20
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# Manage Project-Local Overrides
|
|
20
|
+
|
|
21
|
+
## Goal
|
|
22
|
+
|
|
23
|
+
Answer, from shipped documentation and tooling, the three override questions: which ace-managed documents can this
|
|
24
|
+
project override, where do the project-local files live, and who wins when a project-local file and a gem file share
|
|
25
|
+
the same name. This workflow is the canonical reference; `ace-nav resolve --why` makes the same precedence observable
|
|
26
|
+
per URI.
|
|
27
|
+
|
|
28
|
+
## Prerequisites
|
|
29
|
+
|
|
30
|
+
- Understanding of handbook asset conventions (workflows, cookbooks, guides, skills)
|
|
31
|
+
- `ace-nav` and `ace-handbook` available in the project bundle
|
|
32
|
+
|
|
33
|
+
## Override Surfaces
|
|
34
|
+
|
|
35
|
+
| Surface | Protocol | Project-local location | Overridable? | Winner of duplicates (shipped defaults) |
|
|
36
|
+
|---------|----------|------------------------|--------------|------------------------------------------|
|
|
37
|
+
| Workflows | `wfi://` | `.ace-handbook/workflow-instructions/<ns>/<name>.wf.md` | Yes | Project-local, when its source priority is lower than the gem source's (e.g. the shipped `local` source at priority 5 vs gem at 10) |
|
|
38
|
+
| Cookbooks | `cookbook://` | `.ace-handbook/cookbooks/<name>.cookbook.md` | Yes | The candidate from the source with the lower priority number. The shipped project-local default registers priority 20 and the gem source 10, so **the gem wins by default**; register a project cookbook source with priority < 10 to make project-local win |
|
|
39
|
+
| Guides | `guide://` | `handbook/guides/<name>.g.md` | Yes, with registration | Requires a project source registration (`.ace/nav/protocols/guide-sources/local.yml` → `handbook/guides`); with priority < 10 the project-local guide wins. Without registration the gem wins |
|
|
40
|
+
| Skills | `skill://` | none | **No** | Always the gem. Skills are thin layers; override the workflow the skill calls instead |
|
|
41
|
+
| `.agents/skills/` projection | — | written by `ace-handbook sync` | Never edit | Not a resolution source. Neutral `agents`-provider mirror regenerated (and stale entries pruned) on every sync; it does not ship |
|
|
42
|
+
|
|
43
|
+
## Resolution Rule
|
|
44
|
+
|
|
45
|
+
Sources for a protocol are scanned in ascending priority order (lower number = higher precedence); the first source
|
|
46
|
+
containing the resource wins and remaining duplicates are ignored. Registration defaults: project sources 10, user
|
|
47
|
+
sources 50, gem defaults 100 — an individual source registration may pin another value (this is why the shipped
|
|
48
|
+
`local` workflow source at priority 5 beats the gem at 10, while the shipped project-local cookbook source at 20 loses
|
|
49
|
+
to the gem at 10).
|
|
50
|
+
|
|
51
|
+
To answer "why did this candidate win?" for a concrete URI without planting probe files:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
ace-nav resolve wfi://release/publish --why
|
|
55
|
+
# uri: wfi://release/publish
|
|
56
|
+
# rule: Candidates are tried in ascending source-priority order ...
|
|
57
|
+
# candidates (resolution order):
|
|
58
|
+
# 1. [priority 5] @local (project) → ... ← winner
|
|
59
|
+
# 2. [priority 10] @ace-handbook (gem-default) → ...
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Skills Are Not Overridable
|
|
63
|
+
|
|
64
|
+
Skills (`skill://`) are thin launch layers that load and execute a workflow; they carry no logic worth overriding.
|
|
65
|
+
Consequently no project-local skill source exists: competitors planted in `.ace-handbook/skills/` or `.agents/skills/`
|
|
66
|
+
are ignored and resolution always returns the gem file. The design rule is: **override the workflow the skill calls**
|
|
67
|
+
(under `.ace-handbook/workflow-instructions/`), not the skill. To change a skill's own wording, change the canonical
|
|
68
|
+
`handbook/skills/<name>/SKILL.md` in the owning gem and run `ace-handbook sync`.
|
|
69
|
+
|
|
70
|
+
## Process Steps
|
|
71
|
+
|
|
72
|
+
1. **Identify the surface** you need to override (workflow, cookbook, or guide) and its canonical gem path.
|
|
73
|
+
2. **Create the project-local file** at the surface's project-local location (see table), keeping the canonical
|
|
74
|
+
frontmatter and structure so tools keep resolving it.
|
|
75
|
+
3. **Ensure the source registration makes your file win:**
|
|
76
|
+
- workflows: keep `.ace/nav/protocols/wfi-sources/local.yml` priority below the gem source (shipped default: 5).
|
|
77
|
+
- cookbooks: register `.ace/nav/protocols/cookbook-sources/local.yml` with priority < 10 (the shipped
|
|
78
|
+
project-local default of 20 loses to the gem's 10).
|
|
79
|
+
- guides: register `.ace/nav/protocols/guide-sources/local.yml` pointing at `handbook/guides` with priority < 10;
|
|
80
|
+
without a registration the project-local file is never the winner.
|
|
81
|
+
4. **Verify precedence with `ace-nav resolve <uri> --why`:** your file must be listed first and marked
|
|
82
|
+
`← winner`. For a list of all duplicate candidates use `ace-nav list '<protocol>://<name>'`.
|
|
83
|
+
5. **Never override skills or the `.agents/` projection.** Point the skill at an overridden workflow, or edit the
|
|
84
|
+
canonical skill in its gem.
|
|
85
|
+
6. **Document the override** in the project's own agent guidance so maintainers know the file shadows a gem asset and
|
|
86
|
+
should be re-checked on gem upgrades.
|
|
87
|
+
|
|
88
|
+
## Success Criteria
|
|
89
|
+
|
|
90
|
+
- [ ] Each project-local override file lives at the canonical project-local path for its surface.
|
|
91
|
+
- [ ] `ace-nav resolve <uri> --why` shows the project-local candidate first, marked `← winner` (or, for skills, shows
|
|
92
|
+
the gem candidate only).
|
|
93
|
+
- [ ] No files were planted in `.ace-handbook/skills/` or `.agents/skills/` expecting resolution changes.
|
|
94
|
+
- [ ] `ace-handbook sync` runs clean after any canonical skill change.
|
|
95
|
+
|
|
96
|
+
## Validation Commands
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
ace-bundle wfi://handbook/manage-overrides
|
|
100
|
+
ace-nav resolve wfi://release/publish --why
|
|
101
|
+
ace-nav list 'cookbook://setup-starting-a-multi-ruby-gem-monorepo-with-ace'
|
|
102
|
+
ace-handbook sync
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Planted-Competitor Verification Probes
|
|
106
|
+
|
|
107
|
+
Executed against ace-handbook 0.31.1 / ace-support-nav 0.28.6 (2026-09-20). Plant a project-local competitor file,
|
|
108
|
+
run the resolve, inspect the first line of the winner, then remove the plant:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
# Workflows: project-local WINS
|
|
112
|
+
mkdir -p .ace-handbook/workflow-instructions/handbook
|
|
113
|
+
printf '# OVERRIDE\n' > .ace-handbook/workflow-instructions/handbook/manage-cookbooks.wf.md
|
|
114
|
+
ace-nav resolve wfi://handbook/manage-cookbooks # → .ace-handbook/... (project-local)
|
|
115
|
+
rm .ace-handbook/workflow-instructions/handbook/manage-cookbooks.wf.md
|
|
116
|
+
|
|
117
|
+
# Cookbooks: with shipped defaults the GEM wins (project-local source priority 20 > gem 10)
|
|
118
|
+
mkdir -p .ace-handbook/cookbooks
|
|
119
|
+
printf '# OVERRIDE\n' > .ace-handbook/cookbooks/setup-starting-a-multi-ruby-gem-monorepo-with-ace.cookbook.md
|
|
120
|
+
ace-nav resolve cookbook://setup-starting-a-multi-ruby-gem-monorepo-with-ace # → ace-handbook/handbook/... (gem)
|
|
121
|
+
ace-nav list 'cookbook://setup-starting-a-multi-ruby-gem-monorepo-with-ace' # both candidates listed
|
|
122
|
+
rm .ace-handbook/cookbooks/setup-starting-a-multi-ruby-gem-monorepo-with-ace.cookbook.md
|
|
123
|
+
|
|
124
|
+
# Skills: gem WINS even with competitors in both overlay locations
|
|
125
|
+
mkdir -p .ace-handbook/skills/as-review-pr .agents/skills/as-review-pr
|
|
126
|
+
printf '# OVERRIDE\n' > .ace-handbook/skills/as-review-pr/SKILL.md
|
|
127
|
+
printf '# OVERRIDE\n' > .agents/skills/as-review-pr/SKILL.md
|
|
128
|
+
ace-nav resolve skill://as-review-pr # → ace-review/handbook/skills/... (gem)
|
|
129
|
+
rm .ace-handbook/skills/as-review-pr/SKILL.md
|
|
130
|
+
rmdir .ace-handbook/skills/as-review-pr .ace-handbook/skills
|
|
131
|
+
ace-handbook sync # regenerates .agents/skills/ and prunes the plant
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Error Handling
|
|
135
|
+
|
|
136
|
+
- **Override silently loses:** inspect `ace-nav resolve <uri> --why`; the losing candidate's source priority is too
|
|
137
|
+
high — lower it in the source registration.
|
|
138
|
+
- **Guide override not discovered at all:** no project guide source is registered; add
|
|
139
|
+
`.ace/nav/protocols/guide-sources/local.yml` (see Process Step 3).
|
|
140
|
+
- **Skill edit appears to do nothing:** skills are not overridable; override the workflow the skill calls.
|
|
141
|
+
- **`.agents/skills/` edits keep disappearing:** expected — `ace-handbook sync` regenerates the projection and prunes
|
|
142
|
+
stale entries; edit canonical skills in the owning gem.
|
|
@@ -22,9 +22,11 @@ module Ace
|
|
|
22
22
|
return if quiet
|
|
23
23
|
|
|
24
24
|
results.each do |result|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
line = "synced #{result[:provider]} -> #{result[:relative_output_dir]} " \
|
|
26
|
+
"(#{result[:projected_skills]} skills, #{result[:updated_files]} updated, " \
|
|
27
|
+
"#{result[:removed_entries]} removed)"
|
|
28
|
+
line += format_prompts_summary(result) if result.key?(:relative_prompts_dir)
|
|
29
|
+
puts line
|
|
28
30
|
end
|
|
29
31
|
print_inventory_summary(results)
|
|
30
32
|
rescue => e
|
|
@@ -33,6 +35,12 @@ module Ace
|
|
|
33
35
|
|
|
34
36
|
private
|
|
35
37
|
|
|
38
|
+
def format_prompts_summary(result)
|
|
39
|
+
"; prompts #{result[:relative_prompts_dir]} " \
|
|
40
|
+
"(#{result[:projected_prompts]} prompts, #{result[:updated_prompt_files]} updated, " \
|
|
41
|
+
"#{result[:removed_prompt_entries]} removed)"
|
|
42
|
+
end
|
|
43
|
+
|
|
36
44
|
def print_inventory_summary(results)
|
|
37
45
|
source_breakdown = results.first&.fetch(:source_breakdown, {})
|
|
38
46
|
return if source_breakdown.nil? || source_breakdown.empty?
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ace
|
|
4
|
+
module Handbook
|
|
5
|
+
module Models
|
|
6
|
+
class PromptTemplate
|
|
7
|
+
attr_reader :provider, :source_path, :frontmatter, :content
|
|
8
|
+
|
|
9
|
+
def initialize(provider:, source_path:, frontmatter:, content:)
|
|
10
|
+
@provider = provider
|
|
11
|
+
@source_path = source_path
|
|
12
|
+
@frontmatter = frontmatter
|
|
13
|
+
@content = content
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def name
|
|
17
|
+
File.basename(source_path, ".md")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def source
|
|
21
|
+
frontmatter["source"].to_s
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
|
|
5
|
+
module Ace
|
|
6
|
+
module Handbook
|
|
7
|
+
module Organisms
|
|
8
|
+
# Discovers canonical provider prompt templates shipped by integration packages.
|
|
9
|
+
#
|
|
10
|
+
# Each integration package owns its provider's prompt templates in
|
|
11
|
+
# handbook/prompts/*.md; they project into the provider manifest's prompts_dir.
|
|
12
|
+
class PromptTemplateInventory
|
|
13
|
+
FRONTMATTER_PATTERN = /\A---\s*\n(.*?)\n---\s*\n?(.*)\z/m
|
|
14
|
+
PACKAGE_GLOB = "ace-handbook-integration-*"
|
|
15
|
+
GEM_NAME_PREFIX = "ace-handbook-integration-"
|
|
16
|
+
|
|
17
|
+
attr_reader :project_root
|
|
18
|
+
|
|
19
|
+
def initialize(project_root: Ace::Handbook.project_root, gem_roots: nil)
|
|
20
|
+
@project_root = project_root
|
|
21
|
+
@gem_roots = gem_roots
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def all
|
|
25
|
+
package_roots.filter_map { |root, provider| parse_templates(root, provider) }
|
|
26
|
+
.each_with_object({}) do |(provider, templates), index|
|
|
27
|
+
index[provider] = (index[provider] || []).concat(templates)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def for_provider(provider)
|
|
32
|
+
all.fetch(provider.to_s, [])
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def package_roots
|
|
38
|
+
(local_package_roots + installed_package_roots).sort.uniq.filter_map do |root|
|
|
39
|
+
provider = package_provider(root)
|
|
40
|
+
next if provider.nil? || provider.empty?
|
|
41
|
+
|
|
42
|
+
[root, provider]
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def local_package_roots
|
|
47
|
+
Dir.glob(File.join(project_root, PACKAGE_GLOB)).select { |path| File.directory?(path) }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def installed_package_roots
|
|
51
|
+
return @gem_roots unless @gem_roots.nil?
|
|
52
|
+
|
|
53
|
+
Gem::Specification.find_all
|
|
54
|
+
.select { |spec| spec.name.start_with?(GEM_NAME_PREFIX) }
|
|
55
|
+
.map(&:full_gem_path)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def package_provider(root)
|
|
59
|
+
manifests = Dir.glob(File.join(root, ".ace-defaults", "handbook", "providers", "*.yml")).sort
|
|
60
|
+
return nil if manifests.empty?
|
|
61
|
+
|
|
62
|
+
data = YAML.safe_load_file(manifests.first, permitted_classes: [Date, Time], aliases: true) || {}
|
|
63
|
+
data["provider"].to_s
|
|
64
|
+
rescue StandardError
|
|
65
|
+
nil
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def parse_templates(root, provider)
|
|
69
|
+
templates = Dir.glob(File.join(root, "handbook", "prompts", "*.md")).sort.filter_map do |path|
|
|
70
|
+
match = File.read(path).match(FRONTMATTER_PATTERN)
|
|
71
|
+
frontmatter = match ? parse_frontmatter(match[1]) : {}
|
|
72
|
+
Models::PromptTemplate.new(provider: provider, source_path: path, frontmatter: frontmatter, content: File.read(path))
|
|
73
|
+
end
|
|
74
|
+
[provider, templates]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def parse_frontmatter(raw)
|
|
78
|
+
YAML.safe_load(raw, permitted_classes: [Date, Time], aliases: true) || {}
|
|
79
|
+
rescue StandardError
|
|
80
|
+
{}
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -1,22 +1,27 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "fileutils"
|
|
4
|
+
require "yaml"
|
|
4
5
|
|
|
5
6
|
module Ace
|
|
6
7
|
module Handbook
|
|
7
8
|
module Organisms
|
|
8
9
|
class ProviderSyncer
|
|
9
|
-
|
|
10
|
+
PROJECTION_SOURCE_PREFIX = "ace-handbook-integration-"
|
|
11
|
+
|
|
12
|
+
attr_reader :project_root, :registry, :inventory, :prompt_inventory, :config
|
|
10
13
|
|
|
11
14
|
def initialize(
|
|
12
15
|
project_root: Ace::Handbook.project_root,
|
|
13
16
|
registry: nil,
|
|
14
17
|
inventory: nil,
|
|
18
|
+
prompt_inventory: nil,
|
|
15
19
|
config: nil
|
|
16
20
|
)
|
|
17
21
|
@project_root = project_root
|
|
18
22
|
@registry = registry || Atoms::ProviderRegistry.new(project_root: project_root)
|
|
19
23
|
@inventory = inventory || SkillInventory.new(project_root: project_root)
|
|
24
|
+
@prompt_inventory = prompt_inventory || PromptTemplateInventory.new(project_root: project_root)
|
|
20
25
|
@config = config || Ace::Handbook.config.resolve_namespace("handbook").to_h
|
|
21
26
|
end
|
|
22
27
|
|
|
@@ -31,6 +36,14 @@ module Ace
|
|
|
31
36
|
private
|
|
32
37
|
|
|
33
38
|
def sync_provider(provider, skills:, source_breakdown:)
|
|
39
|
+
skills_result = sync_skills(provider, skills: skills, source_breakdown: source_breakdown)
|
|
40
|
+
prompts_result = sync_prompts(provider)
|
|
41
|
+
return skills_result if prompts_result.nil?
|
|
42
|
+
|
|
43
|
+
skills_result.merge(prompts_result)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def sync_skills(provider, skills:, source_breakdown:)
|
|
34
47
|
output_dir = File.join(project_root, registry.output_dir(provider))
|
|
35
48
|
prepare_output_dir(output_dir)
|
|
36
49
|
|
|
@@ -64,6 +77,35 @@ module Ace
|
|
|
64
77
|
}
|
|
65
78
|
end
|
|
66
79
|
|
|
80
|
+
def sync_prompts(provider)
|
|
81
|
+
prompts_dir = registry.prompts_dir(provider)
|
|
82
|
+
return nil if prompts_dir.nil? || prompts_dir.to_s.empty?
|
|
83
|
+
|
|
84
|
+
output_dir = File.join(project_root, prompts_dir)
|
|
85
|
+
FileUtils.mkdir_p(output_dir)
|
|
86
|
+
|
|
87
|
+
expected = {}
|
|
88
|
+
updated_files = 0
|
|
89
|
+
|
|
90
|
+
prompt_inventory.for_provider(provider).each do |template|
|
|
91
|
+
output_path = File.join(output_dir, "#{template.name}.md")
|
|
92
|
+
expected[template.name] = output_path
|
|
93
|
+
next if File.exist?(output_path) && File.read(output_path) == template.content
|
|
94
|
+
|
|
95
|
+
File.write(output_path, template.content)
|
|
96
|
+
updated_files += 1
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
removed_entries = prune_stale_prompt_files(output_dir, expected.keys)
|
|
100
|
+
|
|
101
|
+
{
|
|
102
|
+
relative_prompts_dir: prompts_dir,
|
|
103
|
+
projected_prompts: expected.size,
|
|
104
|
+
updated_prompt_files: updated_files,
|
|
105
|
+
removed_prompt_entries: removed_entries
|
|
106
|
+
}
|
|
107
|
+
end
|
|
108
|
+
|
|
67
109
|
def summarize_sources(skills)
|
|
68
110
|
skills.each_with_object(Hash.new(0)) do |skill, memo|
|
|
69
111
|
source = skill.source.to_s
|
|
@@ -128,6 +170,28 @@ module Ace
|
|
|
128
170
|
stale.each { |path| FileUtils.rm_rf(path) }
|
|
129
171
|
stale.size
|
|
130
172
|
end
|
|
173
|
+
|
|
174
|
+
# The prompts dir is shared with user-authored templates, so only files
|
|
175
|
+
# carrying an ACE integration provenance marker are ever pruned.
|
|
176
|
+
def prune_stale_prompt_files(output_dir, expected_template_names)
|
|
177
|
+
stale = Dir.glob(File.join(output_dir, "*.md")).reject do |path|
|
|
178
|
+
expected_template_names.include?(File.basename(path, ".md"))
|
|
179
|
+
end.select do |path|
|
|
180
|
+
template_source(path).start_with?(PROJECTION_SOURCE_PREFIX)
|
|
181
|
+
end
|
|
182
|
+
stale.each { |path| FileUtils.rm(path) }
|
|
183
|
+
stale.size
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def template_source(path)
|
|
187
|
+
match = File.read(path).match(/\A---\s*\n(.*?)\n---/m)
|
|
188
|
+
return "" unless match
|
|
189
|
+
|
|
190
|
+
frontmatter = YAML.safe_load(match[1], permitted_classes: [Date, Time], aliases: true)
|
|
191
|
+
frontmatter.is_a?(Hash) ? frontmatter["source"].to_s : ""
|
|
192
|
+
rescue StandardError
|
|
193
|
+
""
|
|
194
|
+
end
|
|
131
195
|
end
|
|
132
196
|
end
|
|
133
197
|
end
|
data/lib/ace/handbook/version.rb
CHANGED
data/lib/ace/handbook.rb
CHANGED
|
@@ -5,8 +5,10 @@ require "ace/support/config"
|
|
|
5
5
|
require_relative "handbook/version"
|
|
6
6
|
require_relative "handbook/atoms/provider_registry"
|
|
7
7
|
require_relative "handbook/models/skill_document"
|
|
8
|
+
require_relative "handbook/models/prompt_template"
|
|
8
9
|
require_relative "handbook/molecules/skill_projection"
|
|
9
10
|
require_relative "handbook/organisms/skill_inventory"
|
|
11
|
+
require_relative "handbook/organisms/prompt_template_inventory"
|
|
10
12
|
require_relative "handbook/organisms/provider_syncer"
|
|
11
13
|
require_relative "handbook/organisms/status_collector"
|
|
12
14
|
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ace-handbook
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.32.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Michal Czyz
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-09-
|
|
10
|
+
date: 2026-09-27 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: ace-support-config
|
|
@@ -169,6 +169,7 @@ files:
|
|
|
169
169
|
- handbook/skills/as-handbook-manage-agents/SKILL.md
|
|
170
170
|
- handbook/skills/as-handbook-manage-cookbooks/SKILL.md
|
|
171
171
|
- handbook/skills/as-handbook-manage-guides/SKILL.md
|
|
172
|
+
- handbook/skills/as-handbook-manage-overrides/SKILL.md
|
|
172
173
|
- handbook/skills/as-handbook-manage-workflows/SKILL.md
|
|
173
174
|
- handbook/skills/as-handbook-parallel-research/SKILL.md
|
|
174
175
|
- handbook/skills/as-handbook-perform-delivery/SKILL.md
|
|
@@ -188,6 +189,7 @@ files:
|
|
|
188
189
|
- handbook/workflow-instructions/handbook/manage-agents.wf.md
|
|
189
190
|
- handbook/workflow-instructions/handbook/manage-cookbooks.wf.md
|
|
190
191
|
- handbook/workflow-instructions/handbook/manage-guides.wf.md
|
|
192
|
+
- handbook/workflow-instructions/handbook/manage-overrides.wf.md
|
|
191
193
|
- handbook/workflow-instructions/handbook/manage-workflows.wf.md
|
|
192
194
|
- handbook/workflow-instructions/handbook/parallel-research.wf.md
|
|
193
195
|
- handbook/workflow-instructions/handbook/perform-delivery.wf.md
|
|
@@ -207,8 +209,10 @@ files:
|
|
|
207
209
|
- lib/ace/handbook/cli.rb
|
|
208
210
|
- lib/ace/handbook/cli/commands/status.rb
|
|
209
211
|
- lib/ace/handbook/cli/commands/sync.rb
|
|
212
|
+
- lib/ace/handbook/models/prompt_template.rb
|
|
210
213
|
- lib/ace/handbook/models/skill_document.rb
|
|
211
214
|
- lib/ace/handbook/molecules/skill_projection.rb
|
|
215
|
+
- lib/ace/handbook/organisms/prompt_template_inventory.rb
|
|
212
216
|
- lib/ace/handbook/organisms/provider_syncer.rb
|
|
213
217
|
- lib/ace/handbook/organisms/skill_inventory.rb
|
|
214
218
|
- lib/ace/handbook/organisms/status_collector.rb
|