@flydocs/cli 0.6.0-alpha.24 → 0.6.0-alpha.26

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flydocs/cli",
3
- "version": "0.6.0-alpha.24",
3
+ "version": "0.6.0-alpha.26",
4
4
  "type": "module",
5
5
  "description": "FlyDocs AI CLI — install, setup, and manage FlyDocs projects",
6
6
  "bin": {
@@ -84,7 +84,17 @@ def get_active_issue_context() -> str | None:
84
84
 
85
85
 
86
86
  def get_config_freshness() -> str | None:
87
- """Warn if validation cache is stale or missing."""
87
+ """Warn if validation cache is stale or missing (v1 configs)."""
88
+ # v2 configs use get_config_freshness_v2() instead
89
+ config_file = Path('.flydocs/config.json')
90
+ if config_file.exists():
91
+ try:
92
+ config = json.loads(config_file.read_text())
93
+ if config.get('configFormat') == 2:
94
+ return None # Skip v1 check for v2 configs
95
+ except (json.JSONDecodeError, OSError):
96
+ pass
97
+
88
98
  cache_file = Path('.flydocs/validation-cache.json')
89
99
  if not cache_file.exists():
90
100
  return None
@@ -102,6 +112,104 @@ def get_config_freshness() -> str | None:
102
112
  return None
103
113
 
104
114
 
115
+ def get_config_freshness_v2() -> str | None:
116
+ """Check config freshness via config/check endpoint (v2 configs only, FLY-540)."""
117
+ config_file = Path('.flydocs/config.json')
118
+ if not config_file.exists():
119
+ return None
120
+
121
+ try:
122
+ config = json.loads(config_file.read_text())
123
+ except (json.JSONDecodeError, OSError):
124
+ return None
125
+
126
+ if config.get('configFormat') != 2 or config.get('tier') != 'cloud':
127
+ return None
128
+
129
+ # Resolve API key: env var > global credentials
130
+ api_key = os.environ.get('FLYDOCS_API_KEY')
131
+ if not api_key:
132
+ cred_file = Path.home() / '.flydocs' / 'credentials'
133
+ if cred_file.exists():
134
+ try:
135
+ cred = json.loads(cred_file.read_text())
136
+ api_key = cred.get('apiKey')
137
+ except (json.JSONDecodeError, OSError):
138
+ pass
139
+
140
+ if not api_key:
141
+ return None
142
+
143
+ # Call config/check — lightweight, ~50ms
144
+ base_url = os.environ.get('FLYDOCS_RELAY_URL', 'https://app.flydocs.ai/api/relay').rstrip('/')
145
+ try:
146
+ import urllib.request
147
+ req = urllib.request.Request(
148
+ f'{base_url}/config/check',
149
+ headers={
150
+ 'Authorization': f'Bearer {api_key}',
151
+ 'Accept': 'application/json',
152
+ },
153
+ )
154
+ with urllib.request.urlopen(req, timeout=5) as resp:
155
+ data = json.loads(resp.read().decode('utf-8'))
156
+ except Exception:
157
+ # Server unreachable — silently continue, never block a session
158
+ return None
159
+
160
+ if data.get('needsSync'):
161
+ stale_fields: list[str] = []
162
+ local_config_version = config.get('configVersion', 0)
163
+ if data.get('configVersion', 0) > local_config_version:
164
+ stale_fields.append('config')
165
+ if data.get('templateVersion', 0) > 0:
166
+ stale_fields.append('templates')
167
+ if data.get('contextVersion', 0) > 0:
168
+ stale_fields.append('context')
169
+ detail = f' ({", ".join(stale_fields)})' if stale_fields else ''
170
+ return f'Config updated{detail} — run `flydocs sync`'
171
+
172
+ return None
173
+
174
+
175
+ def check_skills_manifest() -> str | None:
176
+ """Alert-mode skills manifest validation (FLY-541)."""
177
+ config_file = Path('.flydocs/config.json')
178
+ if not config_file.exists():
179
+ return None
180
+
181
+ try:
182
+ config = json.loads(config_file.read_text())
183
+ except (json.JSONDecodeError, OSError):
184
+ return None
185
+
186
+ expected = config.get('skills', {}).get('installed', [])
187
+ if not expected:
188
+ return None
189
+
190
+ skills_dir = Path('.claude/skills')
191
+ if not skills_dir.is_dir():
192
+ return f'Skills directory missing — run `flydocs sync`'
193
+
194
+ # Check for installed directories
195
+ try:
196
+ installed = {d.name for d in skills_dir.iterdir() if d.is_dir()}
197
+ except OSError:
198
+ return None
199
+
200
+ expected_set = set(expected)
201
+ missing = expected_set - installed
202
+ unexpected = installed - expected_set
203
+
204
+ warnings: list[str] = []
205
+ if missing:
206
+ warnings.append(f'Missing skills: {", ".join(sorted(missing))} — run `flydocs sync`')
207
+ if unexpected:
208
+ warnings.append(f'Unexpected skills: {", ".join(sorted(unexpected))}')
209
+
210
+ return ' | '.join(warnings) if warnings else None
211
+
212
+
105
213
  def main() -> None:
106
214
  """Main hook execution."""
107
215
  try:
@@ -129,6 +237,14 @@ def main() -> None:
129
237
  if freshness:
130
238
  parts.append(freshness)
131
239
 
240
+ freshness_v2 = get_config_freshness_v2()
241
+ if freshness_v2:
242
+ parts.append(freshness_v2)
243
+
244
+ skills_warning = check_skills_manifest()
245
+ if skills_warning:
246
+ parts.append(skills_warning)
247
+
132
248
  if not parts:
133
249
  print('{}')
134
250
  sys.exit(0)
@@ -14,13 +14,14 @@ The service descriptor serves two roles from a single file:
14
14
  where things are: entry points, shared types, build system, package boundaries.
15
15
  Not exported cross-repo.
16
16
 
17
- Generated by the user's coding agent during `/flydocs-setup` Phase 1.5.
17
+ Generated by the user's coding agent during `/flydocs-setup` Phase 1.5, or by the
18
+ server-side AI scanning pipeline (v2).
18
19
 
19
20
  ## Schema
20
21
 
21
22
  ```typescript
22
23
  interface ServiceDescriptor {
23
- version: 1;
24
+ version: 1 | 2;
24
25
  name: string; // Human-readable service name
25
26
  repoSlug: string; // owner/repo format (matches workspace.repoSlug)
26
27
  purpose: string; // One-sentence description of what this service does
@@ -32,6 +33,10 @@ interface ServiceDescriptor {
32
33
 
33
34
  // Intra-repo orientation (what THIS repo's agent uses)
34
35
  structure: ServiceStructure;
36
+
37
+ // v2 fields (present when version is 2, optional for backward compat)
38
+ generatedBy?: "server" | "agent"; // Who generated this descriptor
39
+ generatedAt?: string; // ISO 8601 timestamp of generation
35
40
  }
36
41
 
37
42
  interface ApiSurface {
@@ -63,12 +68,16 @@ interface PackageInfo {
63
68
 
64
69
  ## Field Notes
65
70
 
66
- - `version` is always `1`. Will increment on breaking schema changes.
71
+ - `version` is `1` (agent-generated, legacy) or `2` (supports server generation).
72
+ All consumers accept both versions. The v2 fields are additive — v1 descriptors
73
+ remain valid and fully functional.
67
74
  - `repoSlug` must match the slug registered in the workspace dashboard.
68
75
  - `structure` is local-only — not pushed to relay or included in workspace composite.
69
76
  - `apis` and `dependencies` create PROVIDES/CONSUMES edges in the graph.
70
77
  - `stack` is a flat array of lowercase identifiers (framework names, languages).
71
- - Agent scans the codebase to populate all fields during setup Phase 1.5.
78
+ - `generatedBy` distinguishes server-generated (AI scanning pipeline) from
79
+ agent-generated (local `/flydocs-setup`) descriptors. Absent on v1 descriptors.
80
+ - `generatedAt` tracks freshness for server-generated descriptors. ISO 8601 format.
72
81
 
73
82
  ## Examples
74
83
 
@@ -219,7 +219,7 @@ def scan_service_descriptor(root):
219
219
  except (json.JSONDecodeError, OSError):
220
220
  return nodes, edges
221
221
 
222
- if data.get("version") != 1:
222
+ if data.get("version") not in (1, 2):
223
223
  return nodes, edges
224
224
 
225
225
  repo_slug = data.get("repoSlug", "")
@@ -290,7 +290,7 @@ def scan_sibling_descriptors(root):
290
290
  except (json.JSONDecodeError, OSError):
291
291
  continue
292
292
 
293
- if data.get("version") != 1:
293
+ if data.get("version") not in (1, 2):
294
294
  continue
295
295
 
296
296
  repo_slug = data.get("repoSlug", "")
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.6.0-alpha.24",
2
+ "version": "0.6.0-alpha.26",
3
3
  "sourceRepo": "github.com/plastrlab/flydocs-core",
4
4
  "tier": "local",
5
5
  "setupComplete": false,
@@ -1 +1 @@
1
- 0.6.0-alpha.24
1
+ 0.6.0-alpha.26
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.6.0-alpha.24",
2
+ "version": "0.6.0-alpha.26",
3
3
  "description": "FlyDocs Core - Manifest of all managed files",
4
4
  "repository": "github.com/plastrlab/flydocs-core",
5
5