|
|
@@ -11,16 +11,29 @@
|
|
|
### Goals
|
|
|
|
|
|
- Provide a single POST endpoint that returns path suggestions with metadata (type, path, label, description, grant)
|
|
|
-- Phase 1 (MVP): Return personal memo path with fixed metadata — zero external dependencies
|
|
|
-- Phase 2: Add search-based and category-based suggestions using GROWI AI keyword extraction and search service
|
|
|
+- Phase 1 (MVP): Return personal memo path with fixed metadata — zero external dependencies. **Implemented.**
|
|
|
+- Phase 2: Add AI-powered search-based suggestions with flow/stock information classification, multi-candidate evaluation, and intelligent path proposals including new paths
|
|
|
- Enable independent access control via separate namespace from `/page`
|
|
|
|
|
|
+### Design Principles
|
|
|
+
|
|
|
+- **Client LLM independence**: Heavy reasoning (content analysis, candidate evaluation, path proposal, description generation) is centralized in GROWI AI on the server side. The API response includes structured data fields (`informationType`, `type`, `grant`) alongside natural language (`description`) so that even less capable LLM clients can make correct decisions through simple field access, without requiring advanced reasoning to interpret the response.
|
|
|
+
|
|
|
### Non-Goals
|
|
|
|
|
|
- Page creation or saving (existing `POST /_api/v3/page` handles this)
|
|
|
- Page title suggestion (Claude handles this via user dialogue)
|
|
|
- Client-side "enter manually" option (Agent Skill responsibility)
|
|
|
-- GROWI AI keyword extraction implementation details (separate design)
|
|
|
+
|
|
|
+### Phase 2 Revision Summary
|
|
|
+
|
|
|
+Phase 2 design was revised based on reviewer feedback. Key architectural changes from the prior revision:
|
|
|
+
|
|
|
+1. **AI calls: 1 → 2**: Content analysis (keyword extraction + flow/stock classification) followed by candidate evaluation. Elasticsearch sits between the two calls, making consolidation structurally impossible.
|
|
|
+2. **Candidate selection: mechanical → AI-evaluated**: Instead of top-1 by ES score, multiple candidates are passed to GROWI AI for content-destination fit evaluation.
|
|
|
+3. **Path proposals: 3 patterns**: Parent directory, subdirectory, sibling page (including new paths that don't yet exist).
|
|
|
+4. **Descriptions: mechanical → AI-generated**: Phase 2 descriptions are generated by the candidate evaluator as part of the evaluation.
|
|
|
+5. **Category type: under review**: The existing `category` implementation is retained, but may be merged into the AI evaluation approach after implementation and reviewer discussion.
|
|
|
|
|
|
## Architecture
|
|
|
|
|
|
@@ -33,6 +46,7 @@ The suggest-path endpoint integrates with GROWI's existing API infrastructure:
|
|
|
- **Response format**: `res.apiv3(data)` for success, `res.apiv3Err(error, status)` for errors
|
|
|
- **AI features**: Existing `features/openai/` module with `certifyAiService` middleware for AI-enabled gating
|
|
|
- **Search**: `searchService.searchKeyword()` for full-text search with permission scoping
|
|
|
+- **Flow/stock classification**: Existing `instructionsForInformationTypes` in `features/openai/server/services/assistant/instructions/commons.ts`
|
|
|
|
|
|
No existing architecture needs modification. The endpoint adds a new route namespace alongside existing ones.
|
|
|
|
|
|
@@ -44,15 +58,17 @@ graph TB
|
|
|
MCP[MCP Server]
|
|
|
end
|
|
|
|
|
|
- subgraph GROWI API
|
|
|
+ subgraph GROWI_API[GROWI API]
|
|
|
Router[ai-tools Router]
|
|
|
Handler[suggest-path Handler]
|
|
|
- MemoGen[Memo Suggestion Logic]
|
|
|
- SearchGen[Search Suggestion Logic - Phase 2]
|
|
|
- CategoryGen[Category Suggestion Logic - Phase 2]
|
|
|
+ MemoGen[Memo Suggestion]
|
|
|
+ Analyzer[Content Analyzer - 1st AI Call]
|
|
|
+ Retriever[Search Candidate Retriever]
|
|
|
+ Evaluator[Candidate Evaluator - 2nd AI Call]
|
|
|
+ CategoryGen[Category Suggestion - Under Review]
|
|
|
end
|
|
|
|
|
|
- subgraph Existing Services
|
|
|
+ subgraph Existing[Existing Services]
|
|
|
SearchSvc[Search Service]
|
|
|
GrantSvc[Page Grant Service]
|
|
|
AIFeature[GROWI AI - OpenAI Feature]
|
|
|
@@ -66,23 +82,26 @@ graph TB
|
|
|
MCP -->|POST suggest-path| Router
|
|
|
Router --> Handler
|
|
|
Handler --> MemoGen
|
|
|
- Handler --> SearchGen
|
|
|
+ Handler --> Analyzer
|
|
|
+ Analyzer --> AIFeature
|
|
|
+ Handler --> Retriever
|
|
|
+ Retriever --> SearchSvc
|
|
|
+ Handler --> Evaluator
|
|
|
+ Evaluator --> AIFeature
|
|
|
Handler --> CategoryGen
|
|
|
- SearchGen --> AIFeature
|
|
|
- SearchGen --> SearchSvc
|
|
|
CategoryGen --> SearchSvc
|
|
|
- SearchGen --> GrantSvc
|
|
|
- CategoryGen --> GrantSvc
|
|
|
SearchSvc --> ES
|
|
|
+ Evaluator --> GrantSvc
|
|
|
+ CategoryGen --> GrantSvc
|
|
|
GrantSvc --> Mongo
|
|
|
```
|
|
|
|
|
|
**Architecture Integration**:
|
|
|
|
|
|
-- **Selected pattern**: Layered handler following existing GROWI route conventions. Phase 1 uses inline logic in handler; Phase 2 adds generator functions called by the handler (see [Implementation Paradigm](#implementation-paradigm) for function vs class rationale)
|
|
|
+- **Selected pattern**: Layered handler following existing GROWI route conventions. Phase 1 uses inline logic in handler; Phase 2 adds function components called by the handler (see [Implementation Paradigm](#implementation-paradigm))
|
|
|
- **Domain boundaries**: Route layer (`ai-tools/`) owns the endpoint. Suggestion logic delegates to existing services (search, grant, AI) without modifying them
|
|
|
- **Existing patterns preserved**: Handler factory pattern, middleware chain, `res.apiv3()` response format
|
|
|
-- **New components**: `ai-tools/` route directory (new namespace), `suggest-path.ts` handler
|
|
|
+- **New components**: ContentAnalyzer and CandidateEvaluator wrap GROWI AI calls with suggest-path-specific prompting
|
|
|
- **Steering compliance**: Feature-based separation, named exports, TypeScript strict typing
|
|
|
|
|
|
### Implementation Paradigm
|
|
|
@@ -91,22 +110,22 @@ graph TB
|
|
|
|
|
|
**Class adoption criteria** — a class is permitted only when at least one of the following applies AND a function-based alternative would be clearly inferior:
|
|
|
|
|
|
-1. **Shared dependency management**: Multiple exported functions within a module depend on the same external services (e.g., SearchService), making argument passing across all functions verbose. A class with dependency fields reduces repetition.
|
|
|
-2. **Singleton state/cache management**: The module must maintain mutable state or cached data in a singleton instance, where immutability is not feasible.
|
|
|
+1. **Shared dependency management**: Multiple exported functions within a module depend on the same external services, making argument passing verbose.
|
|
|
+2. **Singleton state/cache management**: The module must maintain mutable state or cached data.
|
|
|
|
|
|
**Component assessment**:
|
|
|
|
|
|
-| Component | Paradigm | Rationale |
|
|
|
-| ------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
|
-| MemoSuggestionGenerator | Function | No external service dependencies beyond `user`. Single function. |
|
|
|
-| DescriptionGenerator | Function | Stateless, no dependencies. Pure transformation functions. |
|
|
|
-| GrantResolver | Function | Single function. Page Model accessed via argument. |
|
|
|
-| KeywordExtractor (Phase 2) | Function | Single function delegating to OpenAI Feature. |
|
|
|
-| SearchSuggestionGenerator (Phase 2) | Function | Single function. SearchService and GrantResolver passed as arguments. |
|
|
|
-| CategorySuggestionGenerator (Phase 2) | Function | Single function. Same dependency pattern as SearchSuggestionGenerator. |
|
|
|
-| SuggestPathService (Phase 2) | Function | Single public function. No state or cache. Dependencies as arguments. May adopt class if public functions grow and shared dependency passing becomes verbose. |
|
|
|
+| Component | Paradigm | Rationale |
|
|
|
+| --- | --- | --- |
|
|
|
+| MemoSuggestionGenerator | Function | No external service dependencies beyond `user`. Single function. |
|
|
|
+| ContentAnalyzer (Phase 2) | Function | Single function delegating to OpenAI Feature. |
|
|
|
+| SearchCandidateRetriever (Phase 2) | Function | Single function. SearchService passed as argument. |
|
|
|
+| CandidateEvaluator (Phase 2) | Function | Single function delegating to OpenAI Feature. GrantResolver passed as argument. |
|
|
|
+| CategorySuggestionGenerator (Phase 2) | Function | Single function. SearchService and GrantResolver passed as arguments. Under review. |
|
|
|
+| GrantResolver | Function | Single function. Page Model accessed via argument. |
|
|
|
+| SuggestPathOrchestrator (Phase 2) | Function | Single public function. Dependencies as arguments. |
|
|
|
|
|
|
-No component currently meets the class adoption criteria. All are implemented as exported functions.
|
|
|
+No component currently meets the class adoption criteria.
|
|
|
|
|
|
### Technology Stack
|
|
|
|
|
|
@@ -114,8 +133,8 @@ No component currently meets the class adoption criteria. All are implemented as
|
|
|
|-------|------------------|-----------------|-------|
|
|
|
| Backend | Express.js (existing) | Route handling, middleware | No new dependencies |
|
|
|
| Validation | express-validator (existing) | Request body validation | Existing pattern |
|
|
|
-| Search | Elasticsearch via searchService (existing) | Phase 2 keyword search | Used for `search` and `category` suggestions |
|
|
|
-| AI | OpenAI feature module (existing) | Phase 2 keyword extraction | Existing `features/openai/` infrastructure |
|
|
|
+| Search | Elasticsearch via searchService (existing) | Phase 2 candidate retrieval | Score threshold filtering added |
|
|
|
+| AI | OpenAI feature module (existing) | Phase 2: 1st call (content analysis), 2nd call (candidate evaluation) | Existing `features/openai/` infrastructure |
|
|
|
| Data | MongoDB via Mongoose (existing) | Page grant lookup | For parent page grant resolution |
|
|
|
|
|
|
No new dependencies introduced. All technology is already in the GROWI stack.
|
|
|
@@ -139,92 +158,122 @@ sequenceDiagram
|
|
|
Handler-->>Client: 200 suggestions array - memo only
|
|
|
```
|
|
|
|
|
|
-### Phase 2 Flow
|
|
|
+### Phase 2 Flow (Revised)
|
|
|
|
|
|
```mermaid
|
|
|
sequenceDiagram
|
|
|
participant Client as MCP Client
|
|
|
- participant Handler as suggest-path Handler
|
|
|
- participant AI as GROWI AI
|
|
|
+ participant Handler as Orchestrator
|
|
|
+ participant AI1 as Content Analyzer
|
|
|
participant Search as Search Service
|
|
|
- participant Grant as Page Grant Service
|
|
|
+ participant AI2 as Candidate Evaluator
|
|
|
+ participant Grant as Grant Resolver
|
|
|
+ participant CatGen as Category Generator
|
|
|
|
|
|
Client->>Handler: POST with body content
|
|
|
Handler->>Handler: Generate memo suggestion
|
|
|
- Handler->>AI: Extract keywords from body
|
|
|
- AI-->>Handler: Keywords array
|
|
|
-
|
|
|
- par Search-based suggestion
|
|
|
- Handler->>Search: searchKeyword with keywords
|
|
|
- Search-->>Handler: Related pages
|
|
|
- Handler->>Grant: Resolve parent grant
|
|
|
- Grant-->>Handler: Grant value
|
|
|
- and Category-based suggestion
|
|
|
- Handler->>Search: searchKeyword with prefix scope
|
|
|
- Search-->>Handler: Top-level pages
|
|
|
- Handler->>Grant: Resolve parent grant
|
|
|
- Grant-->>Handler: Grant value
|
|
|
+
|
|
|
+ Handler->>AI1: Analyze content body
|
|
|
+ Note over AI1: 1st AI Call
|
|
|
+ AI1-->>Handler: keywords + informationType
|
|
|
+
|
|
|
+ par Search and evaluate
|
|
|
+ Handler->>Search: Search by keywords
|
|
|
+ Search-->>Handler: Raw results with scores
|
|
|
+ Handler->>Handler: Filter by score threshold
|
|
|
+ Handler->>AI2: body + analysis + candidates
|
|
|
+ Note over AI2: 2nd AI Call
|
|
|
+ AI2-->>Handler: Evaluated suggestions with paths and descriptions
|
|
|
+ loop For each evaluated suggestion
|
|
|
+ Handler->>Grant: Resolve grant for proposed path
|
|
|
+ Grant-->>Handler: Grant value
|
|
|
+ end
|
|
|
+ and Category suggestion
|
|
|
+ Handler->>CatGen: Generate from keywords
|
|
|
+ CatGen->>Search: Scoped keyword search
|
|
|
+ Search-->>CatGen: Top-level pages
|
|
|
+ CatGen->>Grant: Resolve parent grant
|
|
|
+ Grant-->>CatGen: Grant value
|
|
|
+ CatGen-->>Handler: Category suggestion or null
|
|
|
end
|
|
|
|
|
|
- Handler-->>Client: 200 suggestions array - memo + search + category
|
|
|
+ Handler-->>Client: 200 suggestions array
|
|
|
```
|
|
|
|
|
|
**Key decisions**:
|
|
|
|
|
|
-- Search-based and category-based suggestions are generated in parallel where possible
|
|
|
-- If keyword extraction fails, handler falls back to memo-only response (Phase 1 behavior)
|
|
|
-- If search returns no results for a suggestion type, that type is omitted from the response
|
|
|
+- Content analysis (1st AI call) and candidate evaluation (2nd AI call) are structurally sequential — Elasticsearch sits between them
|
|
|
+- Search-evaluate flow and category generation run in parallel where possible
|
|
|
+- If content analysis fails, handler falls back to memo-only response (Phase 1 behavior)
|
|
|
+- If candidate evaluation fails, handler falls back to memo-only response
|
|
|
+- If search returns no results above the score threshold, search-based suggestions are omitted
|
|
|
+- Category generator runs independently as existing implementation (under review)
|
|
|
|
|
|
## Requirements Traceability
|
|
|
|
|
|
| Requirement | Summary | Components | Interfaces | Flows |
|
|
|
|-------------|---------|------------|------------|-------|
|
|
|
-| 1.1 | POST endpoint returns suggestions array | SuggestPathRouter, SuggestPathHandler | API Contract | Phase 1, Phase 2 |
|
|
|
-| 1.2 | Suggestion fields: type, path, label, description, grant | SuggestPathHandler | PathSuggestion type | — |
|
|
|
-| 1.3 | Path values as directory paths with trailing slash | SuggestPathHandler | PathSuggestion type | — |
|
|
|
+| 1.1 | POST endpoint returns suggestions array | SuggestPathRouter, Orchestrator | API Contract | Phase 1, Phase 2 |
|
|
|
+| 1.2 | Suggestion fields: type, path, label, description, grant | Orchestrator | PathSuggestion type | — |
|
|
|
+| 1.3 | Path values as directory paths with trailing slash | Orchestrator | PathSuggestion type | — |
|
|
|
| 1.4 | Separate namespace from /page | SuggestPathRouter | Route registration | — |
|
|
|
| 2.1 | Include memo type suggestion | MemoSuggestionGenerator | PathSuggestion type | Phase 1 |
|
|
|
-| 2.2 | Memo path under user home directory (user pages enabled) | MemoSuggestionGenerator | — | Phase 1 |
|
|
|
+| 2.2 | Memo path under user home directory | MemoSuggestionGenerator | — | Phase 1 |
|
|
|
| 2.3 | Memo path under alternative namespace (user pages disabled) | MemoSuggestionGenerator | — | Phase 1 |
|
|
|
-| 2.4 | Memo grant = 4 when user pages enabled; resolve from parent when disabled | MemoSuggestionGenerator, GrantResolver | — | — |
|
|
|
-| 2.5 | Fixed description for memo | MemoSuggestionGenerator, DescriptionGenerator | — | — |
|
|
|
-| 3.1 | Search related pages by keywords | SearchSuggestionGenerator | SearchService | Phase 2 |
|
|
|
-| 3.2 | Return parent directory of most relevant page | SearchSuggestionGenerator | — | Phase 2 |
|
|
|
-| 3.3 | Include related page titles in description | SearchSuggestionGenerator, DescriptionGenerator | — | — |
|
|
|
-| 3.4 | Include parent page grant for search type | SearchSuggestionGenerator, GrantResolver | — | — |
|
|
|
-| 3.5 | Omit search type if no results | SearchSuggestionGenerator | — | — |
|
|
|
+| 2.4 | Memo grant = 4 when user pages enabled | MemoSuggestionGenerator, GrantResolver | — | — |
|
|
|
+| 2.5 | Fixed description for memo | MemoSuggestionGenerator | — | — |
|
|
|
+| 3.1 | Search related pages by keywords | SearchCandidateRetriever | SearchService | Phase 2 |
|
|
|
+| 3.2 | Filter candidates by ES score threshold | SearchCandidateRetriever | — | Phase 2 |
|
|
|
+| 3.3 | Pass candidates to AI evaluation | Orchestrator, CandidateEvaluator | — | Phase 2 |
|
|
|
+| 3.4 | Include parent page grant for search-based suggestions | GrantResolver | — | — |
|
|
|
+| 3.5 | Omit search-based suggestions if no results | SearchCandidateRetriever, Orchestrator | — | — |
|
|
|
| 4.1 | Search top-level directories by keywords | CategorySuggestionGenerator | SearchService | Phase 2 |
|
|
|
| 4.2 | Extract top-level path segment | CategorySuggestionGenerator | — | Phase 2 |
|
|
|
| 4.3 | Include parent page grant for category type | CategorySuggestionGenerator, GrantResolver | — | — |
|
|
|
| 4.4 | Omit category type if no results | CategorySuggestionGenerator | — | — |
|
|
|
-| 5.1 | Delegate keyword extraction to GROWI AI | KeywordExtractor | GROWI AI interface | Phase 2 |
|
|
|
-| 5.2 | Use extracted keywords for search, not raw body | SearchSuggestionGenerator, CategorySuggestionGenerator | — | Phase 2 |
|
|
|
-| 5.3 | Fallback to memo if extraction fails | SuggestPathHandler | — | Phase 2 |
|
|
|
-| 6.1 | Description provides selection rationale | DescriptionGenerator | — | — |
|
|
|
-| 6.2 | Fixed text for memo in Phase 1 | DescriptionGenerator | — | — |
|
|
|
-| 6.3 | List page titles for search type in Phase 2 | DescriptionGenerator | — | — |
|
|
|
-| 6.4 | Path segment name for category type in Phase 2 | DescriptionGenerator | — | — |
|
|
|
-| 6.5 | Phase 2 descriptions mechanical, no AI | DescriptionGenerator | — | — |
|
|
|
+| 5.1 | Delegate content analysis to GROWI AI (single call: keywords + flow/stock) | ContentAnalyzer | GROWI AI interface | Phase 2 |
|
|
|
+| 5.2 | Extract 3-5 keywords prioritizing proper nouns | ContentAnalyzer | — | Phase 2 |
|
|
|
+| 5.3 | Use extracted keywords for search, not raw body | SearchCandidateRetriever, CategorySuggestionGenerator | — | Phase 2 |
|
|
|
+| 5.4 | Classify content as flow or stock | ContentAnalyzer | ContentAnalysis type | Phase 2 |
|
|
|
+| 5.5 | Fallback to memo if analysis fails | Orchestrator | — | Phase 2 |
|
|
|
+| 6.1 | Description provides selection rationale | MemoSuggestionGenerator, CandidateEvaluator | — | — |
|
|
|
+| 6.2 | Fixed text for memo in Phase 1 | MemoSuggestionGenerator | — | — |
|
|
|
+| 6.3 | AI-generated description for search-based suggestions | CandidateEvaluator | — | Phase 2 |
|
|
|
| 7.1 | Grant field = parent page grant value | GrantResolver | PageGrant type | — |
|
|
|
-| 7.2 | Grant = upper bound constraint, not recommendation | GrantResolver | — | — |
|
|
|
+| 7.2 | Grant = upper bound constraint | GrantResolver | — | — |
|
|
|
| 8.1 | Require valid API token or login session | SuggestPathRouter | Middleware chain | — |
|
|
|
| 8.2 | Return auth error if unauthenticated | SuggestPathRouter | — | — |
|
|
|
-| 8.3 | Use authenticated user for user-specific suggestions | SuggestPathHandler | — | — |
|
|
|
+| 8.3 | Use authenticated user for user-specific suggestions | Orchestrator | — | — |
|
|
|
| 9.1 | Validation error if body missing/empty | SuggestPathRouter | Validator | — |
|
|
|
-| 9.2 | No internal details in error responses | SuggestPathHandler | ErrorV3 | — |
|
|
|
+| 9.2 | No internal details in error responses | Orchestrator | ErrorV3 | — |
|
|
|
+| 10.1 | Consider flow/stock alignment in candidate evaluation | CandidateEvaluator | ContentAnalysis type | Phase 2 |
|
|
|
+| 10.2 | Identify flow characteristics in candidate locations | CandidateEvaluator | — | Phase 2 |
|
|
|
+| 10.3 | Identify stock characteristics in candidate locations | CandidateEvaluator | — | Phase 2 |
|
|
|
+| 10.4 | Flow/stock as ranking factor, not hard filter | CandidateEvaluator | — | Phase 2 |
|
|
|
+| 11.1 | Evaluate candidates by passing body + path + snippet to AI | CandidateEvaluator | GROWI AI interface | Phase 2 |
|
|
|
+| 11.2 | Rank by content-destination fit and flow/stock alignment | CandidateEvaluator | — | Phase 2 |
|
|
|
+| 11.3 | Generate description per suggestion as part of evaluation | CandidateEvaluator | EvaluatedSuggestion type | Phase 2 |
|
|
|
+| 11.4 | Fallback to memo-only if evaluation fails | Orchestrator | — | Phase 2 |
|
|
|
+| 12.1 | Consider 3 structural patterns: parent, subdirectory, sibling | CandidateEvaluator | — | Phase 2 |
|
|
|
+| 12.2 | Generate new directory names for sibling pattern | CandidateEvaluator | — | Phase 2 |
|
|
|
+| 12.3 | Determine appropriate pattern based on content-destination fit | CandidateEvaluator | — | Phase 2 |
|
|
|
+| 12.4 | Sibling pattern paths at same hierarchy level as candidate | CandidateEvaluator | — | Phase 2 |
|
|
|
+| 13.1 | Include informationType field in search-based suggestions | CandidateEvaluator, Orchestrator | PathSuggestion type | Phase 2 |
|
|
|
+| 13.2 | Provide both structured metadata and natural language context | Orchestrator | PathSuggestion type | Phase 2 |
|
|
|
+| 13.3 | All reasoning-intensive operations server-side | ContentAnalyzer, CandidateEvaluator | — | Phase 2 |
|
|
|
|
|
|
## Components and Interfaces
|
|
|
|
|
|
| Component | Domain/Layer | Intent | Req Coverage | Key Dependencies | Contracts |
|
|
|
|-----------|-------------|--------|--------------|------------------|-----------|
|
|
|
-| SuggestPathRouter | Route | Route registration and middleware composition | 1.4, 8.1, 8.2, 9.1 | Express Router (P0) | API |
|
|
|
-| SuggestPathHandler | Route | Orchestrate suggestion generation and response | 1.1, 1.2, 1.3, 5.3, 8.3, 9.2 | SuggestionGenerators (P0) | API, Service |
|
|
|
-| MemoSuggestionGenerator | Service | Generate memo path suggestion from user identity | 2.1, 2.2, 2.3, 2.4 | req.user (P0) | Service |
|
|
|
-| SearchSuggestionGenerator | Service | Generate search-based suggestion from keywords (Phase 2) | 3.1-3.5, 5.2 | SearchService (P0), GrantResolver (P1) | Service |
|
|
|
-| CategorySuggestionGenerator | Service | Generate category-based suggestion from keywords (Phase 2) | 4.1-4.4, 5.2 | SearchService (P0), GrantResolver (P1) | Service |
|
|
|
-| KeywordExtractor | Service | Extract keywords from content via GROWI AI (Phase 2) | 5.1, 5.2 | OpenAI Feature (P0) | Service |
|
|
|
-| DescriptionGenerator | Service | Generate description text per suggestion type | 6.1-6.5 | None | Service |
|
|
|
-| GrantResolver | Service | Resolve parent page grant for a given path | 7.1, 7.2, 3.4, 4.3 | Page Model (P0) | Service |
|
|
|
+| SuggestPathRouter | Route | Route registration and middleware | 1.4, 8.1, 8.2, 9.1 | Express Router (P0) | API |
|
|
|
+| SuggestPathOrchestrator | Service | Orchestrate all suggestion generators | 1.1-1.3, 3.3, 3.5, 5.5, 8.3, 9.2, 11.4, 13.2 | All generators (P0) | Service |
|
|
|
+| MemoSuggestionGenerator | Service | Generate memo path suggestion | 2.1-2.5, 6.2 | req.user (P0) | Service |
|
|
|
+| ContentAnalyzer | Service | Extract keywords + classify flow/stock (1st AI call) | 5.1-5.4 | OpenAI Feature (P0) | Service |
|
|
|
+| SearchCandidateRetriever | Service | Search ES and filter by score threshold | 3.1, 3.2, 3.5 | SearchService (P0) | Service |
|
|
|
+| CandidateEvaluator | Service | AI-evaluate candidates, propose paths, generate descriptions (2nd AI call) | 3.3, 6.3, 10.1-10.4, 11.1-11.3, 12.1-12.4, 13.1 | OpenAI Feature (P0), GrantResolver (P1) | Service |
|
|
|
+| CategorySuggestionGenerator | Service | Generate category suggestion (under review) | 4.1-4.4 | SearchService (P0), GrantResolver (P1) | Service |
|
|
|
+| GrantResolver | Service | Resolve parent page grant for a path | 7.1, 7.2, 3.4, 4.3 | Page Model (P0) | Service |
|
|
|
|
|
|
### Route Layer
|
|
|
|
|
|
@@ -245,7 +294,7 @@ sequenceDiagram
|
|
|
**Dependencies**
|
|
|
|
|
|
- Inbound: MCP Client — HTTP POST requests (P0)
|
|
|
-- Outbound: SuggestPathHandler — request processing (P0)
|
|
|
+- Outbound: SuggestPathOrchestrator — request processing (P0)
|
|
|
- External: Express Router, express-validator — routing and validation (P0)
|
|
|
|
|
|
**Contracts**: API [x]
|
|
|
@@ -259,44 +308,60 @@ sequenceDiagram
|
|
|
**Implementation Notes**
|
|
|
|
|
|
- Route registered in `apps/app/src/server/routes/apiv3/index.js` as `router.use('/ai-tools', ...)`
|
|
|
-- Middleware chain follows existing pattern: `accessTokenParser` → `loginRequiredStrictly` → `certifyAiService` → validators → `apiV3FormValidator` → handler
|
|
|
+- Middleware chain: `accessTokenParser` → `loginRequiredStrictly` → `certifyAiService` → validators → `apiV3FormValidator` → handler
|
|
|
- Namespace `ai-tools` is tentative pending yuki confirmation; change requires single line edit in `index.js`
|
|
|
|
|
|
-#### SuggestPathHandler
|
|
|
+### Service Layer
|
|
|
+
|
|
|
+#### SuggestPathOrchestrator
|
|
|
|
|
|
| Field | Detail |
|
|
|
|-------|--------|
|
|
|
-| Intent | Orchestrate suggestion generation, collect results, return unified response |
|
|
|
-| Requirements | 1.1, 1.2, 1.3, 5.3, 8.3, 9.2 |
|
|
|
+| Intent | Orchestrate all suggestion generators, handle failures with graceful degradation |
|
|
|
+| Requirements | 1.1, 1.2, 1.3, 3.3, 3.5, 5.5, 8.3, 9.2, 11.4 |
|
|
|
|
|
|
**Responsibilities & Constraints**
|
|
|
|
|
|
-- Invoke suggestion generators (memo always; search and category in Phase 2)
|
|
|
+- Always generate memo suggestion first (guaranteed fallback)
|
|
|
+- Invoke ContentAnalyzer, then pass results to SearchCandidateRetriever and CandidateEvaluator
|
|
|
+- Run category generation in parallel with the search-evaluate pipeline
|
|
|
- Collect non-null results into suggestions array
|
|
|
-- Handle errors gracefully: if Phase 2 logic fails, fall back to memo-only
|
|
|
-- Format response using `res.apiv3()`
|
|
|
+- On any Phase 2 failure, fall back to memo-only response
|
|
|
|
|
|
**Dependencies**
|
|
|
|
|
|
- Inbound: SuggestPathRouter — validated request (P0)
|
|
|
-- Outbound: MemoSuggestionGenerator, SearchSuggestionGenerator, CategorySuggestionGenerator, KeywordExtractor — suggestion generation (P0)
|
|
|
+- Outbound: All service layer components — suggestion generation (P0)
|
|
|
|
|
|
**Contracts**: Service [x]
|
|
|
|
|
|
##### Service Interface
|
|
|
|
|
|
```typescript
|
|
|
-// Phase 1: Handler contains inline logic
|
|
|
-// Phase 2: Handler calls generateSuggestions with explicit dependencies
|
|
|
+interface SuggestPathDependencies {
|
|
|
+ analyzeContent: (body: string) => Promise<ContentAnalysis>;
|
|
|
+ retrieveSearchCandidates: (
|
|
|
+ keywords: string[],
|
|
|
+ user: IUserHasId,
|
|
|
+ userGroups: PopulatedGrantedGroup[],
|
|
|
+ ) => Promise<SearchCandidate[]>;
|
|
|
+ evaluateCandidates: (
|
|
|
+ body: string,
|
|
|
+ analysis: ContentAnalysis,
|
|
|
+ candidates: SearchCandidate[],
|
|
|
+ ) => Promise<EvaluatedSuggestion[]>;
|
|
|
+ generateCategorySuggestion: (
|
|
|
+ keywords: string[],
|
|
|
+ user: IUserHasId,
|
|
|
+ userGroups: PopulatedGrantedGroup[],
|
|
|
+ ) => Promise<PathSuggestion | null>;
|
|
|
+ resolveParentGrant: (path: string) => Promise<number>;
|
|
|
+}
|
|
|
|
|
|
function generateSuggestions(
|
|
|
user: IUserHasId,
|
|
|
body: string,
|
|
|
- deps: {
|
|
|
- searchService: SearchService;
|
|
|
- extractKeywords: (body: string) => Promise<string[]>;
|
|
|
- resolveParentGrant: (path: string) => Promise<number>;
|
|
|
- },
|
|
|
+ deps: SuggestPathDependencies,
|
|
|
): Promise<PathSuggestion[]>;
|
|
|
```
|
|
|
|
|
|
@@ -306,168 +371,210 @@ function generateSuggestions(
|
|
|
|
|
|
**Implementation Notes**
|
|
|
|
|
|
-- Phase 1: Logic is inline in handler (memo generation is ~10 lines). The `body` field is required but unused in Phase 1 — this maintains API contract stability so the transition to Phase 2 introduces no breaking changes. The MCP client always has content body available in the save workflow
|
|
|
-- Phase 2: Extract orchestration logic to a `generateSuggestions` function. Dependencies (SearchService, KeywordExtractor, GrantResolver) are passed as arguments. See [Implementation Paradigm](#implementation-paradigm) for class adoption criteria
|
|
|
-- Error handling: Catch Phase 2 failures, log, return memo-only response
|
|
|
-
|
|
|
-### Service Layer
|
|
|
+- Phase 1: Logic is inline in handler (memo generation is ~10 lines). The `body` field is required but unused in Phase 1 — this maintains API contract stability
|
|
|
+- Phase 2: Orchestration function calls content analyzer, then fans out to search-evaluate pipeline and category generator in parallel. Dependencies injected for testability
|
|
|
+- Error handling: Catch Phase 2 failures at orchestration level, log, return memo-only
|
|
|
+- informationType mapping: When building `PathSuggestion` from `EvaluatedSuggestion`, the orchestrator attaches `ContentAnalysis.informationType` to each search-type suggestion (Req 13.1)
|
|
|
|
|
|
#### MemoSuggestionGenerator
|
|
|
|
|
|
| Field | Detail |
|
|
|
|-------|--------|
|
|
|
| Intent | Generate personal memo area path suggestion |
|
|
|
-| Requirements | 2.1, 2.2, 2.3, 2.4, 2.5 |
|
|
|
+| Requirements | 2.1, 2.2, 2.3, 2.4, 2.5, 6.2 |
|
|
|
|
|
|
**Responsibilities & Constraints**
|
|
|
|
|
|
- Check `disableUserPages` configuration via `crowi.configManager`
|
|
|
- When user pages are enabled (default): Generate path `/user/{username}/memo/` using `userHomepagePath(user)` utility, set grant to `PageGrant.GRANT_OWNER` (4)
|
|
|
-- When user pages are disabled: Generate path under alternative namespace (e.g., `/memo/{username}/`), resolve grant from parent page. The exact alternative path is subject to confirmation
|
|
|
+- When user pages are disabled: Generate path under alternative namespace (e.g., `/memo/{username}/`), resolve grant from parent page
|
|
|
- Set fixed description and label text
|
|
|
-- Always succeeds (path can be determined from either configuration)
|
|
|
+- Always succeeds
|
|
|
|
|
|
**Contracts**: Service [x]
|
|
|
|
|
|
##### Service Interface
|
|
|
|
|
|
```typescript
|
|
|
-function generateMemoSuggestion(user: IUserHasId): PathSuggestion {
|
|
|
- // Returns memo suggestion with type 'memo'
|
|
|
-}
|
|
|
+function generateMemoSuggestion(user: IUserHasId): PathSuggestion;
|
|
|
```
|
|
|
|
|
|
- Preconditions: `user` has valid `username` field
|
|
|
-- Postconditions: Returns a `PathSuggestion` with `type: 'memo'`. When user pages are enabled, `grant: 4`; when disabled, grant is resolved from the parent page
|
|
|
+- Postconditions: Returns `PathSuggestion` with `type: 'memo'`, `grant: 4` when user pages enabled
|
|
|
|
|
|
-#### SearchSuggestionGenerator (Phase 2)
|
|
|
+#### ContentAnalyzer (Phase 2)
|
|
|
|
|
|
| Field | Detail |
|
|
|
|-------|--------|
|
|
|
-| Intent | Find related pages via keyword search and suggest their parent directory |
|
|
|
-| Requirements | 3.1, 3.2, 3.3, 3.4, 3.5, 5.2 |
|
|
|
+| Intent | Extract keywords and classify content information type via GROWI AI (1st AI call) |
|
|
|
+| Requirements | 5.1, 5.2, 5.3, 5.4 |
|
|
|
|
|
|
**Responsibilities & Constraints**
|
|
|
|
|
|
-- Call `searchService.searchKeyword()` with extracted keywords
|
|
|
-- Select the top-1 result by Elasticsearch score; extract parent directory from its path
|
|
|
-- Generate description listing up to 3 related page titles (top results by score)
|
|
|
-- Resolve parent page grant via GrantResolver
|
|
|
-- Return `null` if no search results found
|
|
|
-- Note: Selection heuristic (top-1 by score) is the initial approach; may be refined with real-world data during Phase 2 implementation
|
|
|
+- Accept content body string
|
|
|
+- Delegate to GROWI AI (existing OpenAI feature) for a single AI call that performs:
|
|
|
+ - Keyword extraction: 3-5 keywords, prioritizing proper nouns and technical terms
|
|
|
+ - Flow/stock classification: Determine if content is flow information (time-bound: meeting notes, diaries, reports) or stock information (reference: documentation, knowledge base)
|
|
|
+- Return structured `ContentAnalysis` result
|
|
|
+- Existing `instructionsForInformationTypes` in commons.ts serves as reference for AI prompting but is not the sole classification criterion (per reviewer feedback)
|
|
|
|
|
|
**Dependencies**
|
|
|
|
|
|
-- Outbound: SearchService — keyword search (P0)
|
|
|
-- Outbound: GrantResolver — parent page grant lookup (P1)
|
|
|
+- External: OpenAI Feature module — AI inference (P0)
|
|
|
|
|
|
**Contracts**: Service [x]
|
|
|
|
|
|
##### Service Interface
|
|
|
|
|
|
```typescript
|
|
|
-function generateSearchSuggestion(
|
|
|
- keywords: string[],
|
|
|
- user: IUserHasId,
|
|
|
- userGroups: PopulatedGrantedGroup[],
|
|
|
-): Promise<PathSuggestion | null>;
|
|
|
+interface ContentAnalysis {
|
|
|
+ keywords: string[];
|
|
|
+ informationType: 'flow' | 'stock';
|
|
|
+}
|
|
|
+
|
|
|
+function analyzeContent(body: string): Promise<ContentAnalysis>;
|
|
|
```
|
|
|
|
|
|
-- Preconditions: `keywords` is non-empty array
|
|
|
-- Postconditions: Returns `PathSuggestion` with `type: 'search'` or `null` if no results
|
|
|
+- Preconditions: `body` is non-empty string
|
|
|
+- Postconditions: Returns `ContentAnalysis` with 1-5 keywords and informationType
|
|
|
+- Error behavior: Throws on failure; caller handles fallback
|
|
|
|
|
|
-#### CategorySuggestionGenerator (Phase 2)
|
|
|
+#### SearchCandidateRetriever (Phase 2)
|
|
|
|
|
|
| Field | Detail |
|
|
|
|-------|--------|
|
|
|
-| Intent | Find matching top-level category directory for content |
|
|
|
-| Requirements | 4.1, 4.2, 4.3, 4.4, 5.2 |
|
|
|
+| Intent | Search for related pages using keywords and filter by score threshold |
|
|
|
+| Requirements | 3.1, 3.2, 3.5 |
|
|
|
|
|
|
**Responsibilities & Constraints**
|
|
|
|
|
|
-- Call `searchService.searchKeyword()` with keywords scoped to top-level (`prefix:/`)
|
|
|
-- Select the top-1 result by Elasticsearch score; extract top-level path segment (e.g., `/tech-notes/React/hooks` → `/tech-notes/`)
|
|
|
-- Generate description from top-level segment name
|
|
|
-- Resolve parent page grant via GrantResolver
|
|
|
-- Return `null` if no matching top-level pages found
|
|
|
-- Note: Selection heuristic (top-1 by score) is the initial approach; may be refined with real-world data during Phase 2 implementation
|
|
|
+- Call `searchService.searchKeyword()` with extracted keywords
|
|
|
+- Filter results by Elasticsearch score threshold to retain only sufficiently relevant candidates
|
|
|
+- Return array of `SearchCandidate` objects with page path, snippet, and score
|
|
|
+- Return empty array if no results pass the threshold
|
|
|
+- Score threshold value is tunable (determined during implementation with real data)
|
|
|
|
|
|
**Dependencies**
|
|
|
|
|
|
-- Outbound: SearchService — scoped keyword search (P0)
|
|
|
-- Outbound: GrantResolver — parent page grant lookup (P1)
|
|
|
+- Outbound: SearchService — keyword search (P0)
|
|
|
|
|
|
**Contracts**: Service [x]
|
|
|
|
|
|
##### Service Interface
|
|
|
|
|
|
```typescript
|
|
|
-function generateCategorySuggestion(
|
|
|
+interface SearchCandidate {
|
|
|
+ pagePath: string;
|
|
|
+ snippet: string;
|
|
|
+ score: number;
|
|
|
+}
|
|
|
+
|
|
|
+function retrieveSearchCandidates(
|
|
|
keywords: string[],
|
|
|
user: IUserHasId,
|
|
|
userGroups: PopulatedGrantedGroup[],
|
|
|
-): Promise<PathSuggestion | null>;
|
|
|
+): Promise<SearchCandidate[]>;
|
|
|
```
|
|
|
|
|
|
- Preconditions: `keywords` is non-empty array
|
|
|
-- Postconditions: Returns `PathSuggestion` with `type: 'category'` or `null` if no results
|
|
|
+- Postconditions: Returns array of `SearchCandidate` (may be empty). All candidates have scores above the configured threshold
|
|
|
+- Note: Replaces the prior `SearchSuggestionGenerator` which performed top-1 selection and description generation. Those responsibilities moved to `CandidateEvaluator`
|
|
|
|
|
|
-#### KeywordExtractor (Phase 2)
|
|
|
+#### CandidateEvaluator (Phase 2)
|
|
|
|
|
|
| Field | Detail |
|
|
|
|-------|--------|
|
|
|
-| Intent | Extract search-relevant keywords from content body via GROWI AI |
|
|
|
-| Requirements | 5.1, 5.2 |
|
|
|
+| Intent | Evaluate search candidates via GROWI AI for content-destination fit, propose paths, generate descriptions (2nd AI call) |
|
|
|
+| Requirements | 3.3, 6.3, 10.1, 10.2, 10.3, 10.4, 11.1, 11.2, 11.3, 12.1, 12.2, 12.3, 12.4, 13.1 |
|
|
|
|
|
|
**Responsibilities & Constraints**
|
|
|
|
|
|
-- Accept content body string
|
|
|
-- Delegate to GROWI AI (existing OpenAI feature) for keyword extraction
|
|
|
-- Return 3-5 keywords prioritizing proper nouns and technical terms
|
|
|
-- Avoid generic/common words
|
|
|
-- Implementation details are out of scope for this spec (handled in separate GROWI AI design)
|
|
|
+- Accept content body, content analysis (from 1st AI call), and search candidates
|
|
|
+- Delegate to GROWI AI for a single AI call that performs:
|
|
|
+ - **Candidate evaluation**: Assess each candidate's suitability considering content relevance and flow/stock alignment
|
|
|
+ - **Path proposal**: For each suitable candidate, propose a save location using one of 3 structural patterns:
|
|
|
+ - (a) Parent directory of the matching page
|
|
|
+ - (b) Subdirectory under the matching page
|
|
|
+ - (c) Sibling directory alongside the matching page (may generate new path)
|
|
|
+ - **Description generation**: Generate a description for each suggestion explaining why the location is suitable
|
|
|
+ - **Ranking**: Order suggestions by content-destination fit
|
|
|
+- Flow/stock alignment is a ranking factor, not a hard filter (10.4)
|
|
|
+- Sibling pattern (c) may generate paths that don't yet exist in GROWI (12.2). Generated paths must be at the same hierarchy level as the matching search candidate page (12.4)
|
|
|
+- AI context budget: Pass candidate paths + ES snippets, NOT full page bodies (see architecture doc)
|
|
|
+- Resolve grant for each proposed path via GrantResolver after AI evaluation returns
|
|
|
|
|
|
**Dependencies**
|
|
|
|
|
|
- External: OpenAI Feature module — AI inference (P0)
|
|
|
+- Outbound: GrantResolver — grant resolution for proposed paths (P1)
|
|
|
|
|
|
**Contracts**: Service [x]
|
|
|
|
|
|
##### Service Interface
|
|
|
|
|
|
```typescript
|
|
|
-function extractKeywords(body: string): Promise<string[]>;
|
|
|
+interface EvaluatedSuggestion {
|
|
|
+ path: string; // Proposed directory path with trailing /
|
|
|
+ label: string; // Display label
|
|
|
+ description: string; // AI-generated rationale
|
|
|
+}
|
|
|
+
|
|
|
+function evaluateCandidates(
|
|
|
+ body: string,
|
|
|
+ analysis: ContentAnalysis,
|
|
|
+ candidates: SearchCandidate[],
|
|
|
+): Promise<EvaluatedSuggestion[]>;
|
|
|
```
|
|
|
|
|
|
-- Preconditions: `body` is non-empty string
|
|
|
-- Postconditions: Returns array of 0-5 keyword strings
|
|
|
+- Preconditions: `candidates` is non-empty array, `analysis` contains valid informationType
|
|
|
+- Postconditions: Returns array of `EvaluatedSuggestion` ordered by fit score (best first). May return empty array if no candidates are suitable. All paths end with `/`
|
|
|
- Error behavior: Throws on failure; caller handles fallback
|
|
|
+- Note: Grant resolution is performed by the orchestrator after this function returns, not inside this function
|
|
|
+
|
|
|
+**Implementation Notes**
|
|
|
+
|
|
|
+- The 2nd AI call receives: POST body, informationType (from 1st call), and for each candidate: pagePath + ES snippet
|
|
|
+- The AI is instructed to consider the 3 path proposal patterns and select the most appropriate for each candidate
|
|
|
+- Existing `instructionsForInformationTypes` from commons.ts is referenced in the AI prompt as guidance for flow/stock assessment of candidate locations
|
|
|
+- AI prompt design details are deferred to implementation phase
|
|
|
|
|
|
-#### DescriptionGenerator
|
|
|
+#### CategorySuggestionGenerator (Phase 2 — Under Review)
|
|
|
|
|
|
| Field | Detail |
|
|
|
|-------|--------|
|
|
|
-| Intent | Generate human-readable description for each suggestion type |
|
|
|
-| Requirements | 6.1, 6.2, 6.3, 6.4, 6.5 |
|
|
|
+| Intent | Find matching top-level category directory for content |
|
|
|
+| Requirements | 4.1, 4.2, 4.3, 4.4 |
|
|
|
+
|
|
|
+> **Note**: This component has an existing implementation from the prior Phase 2 design. With the introduction of AI-based candidate evaluation, this component may overlap with CandidateEvaluator's path proposal capabilities. Whether to retain, merge, or remove is deferred to post-implementation reviewer discussion. The existing implementation is maintained as-is.
|
|
|
|
|
|
**Responsibilities & Constraints**
|
|
|
|
|
|
-- `memo` type: Return fixed descriptive text (e.g., "Save to your personal memo area")
|
|
|
-- `search` type (Phase 2): List up to 3 related page titles from top search results by score. No AI usage — purely mechanical
|
|
|
-- `category` type (Phase 2): Generate from top-level path segment name. No AI usage — purely mechanical
|
|
|
+- Call `searchService.searchKeyword()` with keywords scoped to top-level (`prefix:/`)
|
|
|
+- Select the top-1 result by Elasticsearch score; extract top-level path segment
|
|
|
+- Generate description from top-level segment name (mechanical, no AI)
|
|
|
+- Resolve parent page grant via GrantResolver
|
|
|
+- Return `null` if no matching top-level pages found
|
|
|
+
|
|
|
+**Dependencies**
|
|
|
+
|
|
|
+- Outbound: SearchService — scoped keyword search (P0)
|
|
|
+- Outbound: GrantResolver — parent page grant lookup (P1)
|
|
|
|
|
|
**Contracts**: Service [x]
|
|
|
|
|
|
##### Service Interface
|
|
|
|
|
|
```typescript
|
|
|
-function generateMemoDescription(): string;
|
|
|
-
|
|
|
-// Phase 2
|
|
|
-function generateSearchDescription(relatedPageTitles: string[]): string; // accepts up to 3 titles
|
|
|
-function generateCategoryDescription(topLevelSegment: string): string;
|
|
|
+function generateCategorySuggestion(
|
|
|
+ keywords: string[],
|
|
|
+ user: IUserHasId,
|
|
|
+ userGroups: PopulatedGrantedGroup[],
|
|
|
+): Promise<PathSuggestion | null>;
|
|
|
```
|
|
|
|
|
|
+- Preconditions: `keywords` is non-empty array
|
|
|
+- Postconditions: Returns `PathSuggestion` with `type: 'category'` or `null` if no results
|
|
|
+
|
|
|
#### GrantResolver
|
|
|
|
|
|
| Field | Detail |
|
|
|
@@ -479,8 +586,9 @@ function generateCategoryDescription(topLevelSegment: string): string;
|
|
|
|
|
|
- Given a directory path, find the corresponding page in MongoDB
|
|
|
- Return its `grant` value as the upper bound for child pages
|
|
|
-- For memo path: always returns `PageGrant.GRANT_OWNER` (4) — can be hardcoded in Phase 1
|
|
|
-- For search/category paths (Phase 2): query Page model for the parent page's grant
|
|
|
+- For memo path: always returns `PageGrant.GRANT_OWNER` (4)
|
|
|
+- For search/category/evaluated paths (Phase 2): query Page model for the parent page's grant
|
|
|
+- For new paths (sibling pattern): traverse upward to find the nearest existing ancestor page's grant
|
|
|
|
|
|
**Dependencies**
|
|
|
|
|
|
@@ -515,26 +623,28 @@ No new database entities. The endpoint reads from existing models only.
|
|
|
|
|
|
```typescript
|
|
|
interface SuggestPathRequest {
|
|
|
- body: string; // Page content for keyword extraction
|
|
|
+ body: string; // Page content for analysis
|
|
|
}
|
|
|
```
|
|
|
|
|
|
**Validation rules**:
|
|
|
|
|
|
- `body`: Required, non-empty string
|
|
|
-- No endpoint-specific maximum length. Body size is governed by GROWI's global Express body-parser configuration. The KeywordExtractor (Phase 2) handles truncation internally if content exceeds its processing capacity
|
|
|
+- No endpoint-specific maximum length. Body size is governed by GROWI's global Express body-parser configuration
|
|
|
|
|
|
#### Response Schema
|
|
|
|
|
|
```typescript
|
|
|
type SuggestionType = 'memo' | 'search' | 'category';
|
|
|
+type InformationType = 'flow' | 'stock';
|
|
|
|
|
|
interface PathSuggestion {
|
|
|
type: SuggestionType;
|
|
|
- path: string; // Directory path with trailing '/'
|
|
|
- label: string; // Display label for the suggestion
|
|
|
- description: string; // Selection rationale
|
|
|
- grant: number; // Parent page grant (PageGrant value)
|
|
|
+ path: string; // Directory path with trailing '/'
|
|
|
+ label: string; // Display label for the suggestion
|
|
|
+ description: string; // Selection rationale (fixed for memo, AI-generated for search)
|
|
|
+ grant: number; // Parent page grant (PageGrant value)
|
|
|
+ informationType?: InformationType; // Content's information type as classified by GROWI AI (Phase 2, search-based only)
|
|
|
}
|
|
|
|
|
|
interface SuggestPathResponse {
|
|
|
@@ -549,6 +659,29 @@ interface SuggestPathResponse {
|
|
|
- `grant` is a valid PageGrant value (1, 2, 4, or 5)
|
|
|
- `type` is one of the defined SuggestionType values
|
|
|
|
|
|
+> **Resolved**: The `informationType` field has been added to `PathSuggestion` as an optional field for search-based suggestions. This supports the Client LLM Independence design principle (Requirement 13) by providing structured metadata that any client can use regardless of reasoning capability.
|
|
|
+
|
|
|
+#### Internal Types (Phase 2)
|
|
|
+
|
|
|
+```typescript
|
|
|
+interface ContentAnalysis {
|
|
|
+ keywords: string[];
|
|
|
+ informationType: 'flow' | 'stock';
|
|
|
+}
|
|
|
+
|
|
|
+interface SearchCandidate {
|
|
|
+ pagePath: string;
|
|
|
+ snippet: string;
|
|
|
+ score: number;
|
|
|
+}
|
|
|
+
|
|
|
+interface EvaluatedSuggestion {
|
|
|
+ path: string; // Proposed directory path with trailing /
|
|
|
+ label: string;
|
|
|
+ description: string; // AI-generated rationale
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
#### Phase 1 Response Example
|
|
|
|
|
|
```json
|
|
|
@@ -579,10 +712,19 @@ interface SuggestPathResponse {
|
|
|
},
|
|
|
{
|
|
|
"type": "search",
|
|
|
- "path": "/tech-notes/React/",
|
|
|
+ "path": "/tech-notes/React/state-management/",
|
|
|
"label": "Save near related pages",
|
|
|
- "description": "Related pages under this directory: React Hooks Guide, Jotai State Management",
|
|
|
- "grant": 1
|
|
|
+ "description": "This area contains pages about React state management including Jotai and Redux. Your stock content fits well alongside this existing reference material.",
|
|
|
+ "grant": 1,
|
|
|
+ "informationType": "stock"
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "type": "search",
|
|
|
+ "path": "/tech-notes/React/backend/",
|
|
|
+ "label": "New section for backend topics",
|
|
|
+ "description": "Related frontend pages exist nearby. This new section organizes your backend content as a sibling to the existing frontend knowledge.",
|
|
|
+ "grant": 1,
|
|
|
+ "informationType": "stock"
|
|
|
},
|
|
|
{
|
|
|
"type": "category",
|
|
|
@@ -595,6 +737,8 @@ interface SuggestPathResponse {
|
|
|
}
|
|
|
```
|
|
|
|
|
|
+Note: Phase 2 may return multiple `search`-type suggestions (one per evaluated candidate). The `category` suggestion appears if the CategorySuggestionGenerator finds a match (component under review).
|
|
|
+
|
|
|
## Error Handling
|
|
|
|
|
|
### Error Categories and Responses
|
|
|
@@ -607,23 +751,33 @@ interface SuggestPathResponse {
|
|
|
| No authentication token/session | 401 | Authentication required | 8.2 |
|
|
|
| AI service not enabled | 403 | GROWI AI is not enabled | 1.4 |
|
|
|
|
|
|
+**System Errors — Graceful Degradation (returns 200)**:
|
|
|
+
|
|
|
+| Error | Behavior | Fallback | Requirement |
|
|
|
+|-------|----------|----------|-------------|
|
|
|
+| Content analysis failure (1st AI call) | Log error, skip search pipeline | Memo suggestion only | 5.5 |
|
|
|
+| Search service failure | Log error, skip search-based suggestions | Memo + category (if available) | 3.5 |
|
|
|
+| Candidate evaluation failure (2nd AI call) | Log error, skip search-based suggestions | Memo + category (if available) | 11.4 |
|
|
|
+| Category generation failure | Log error, skip category suggestion | Memo + search-based (if available) | 4.4 |
|
|
|
+
|
|
|
**System Errors (5xx)**:
|
|
|
|
|
|
-| Error | Status | Response | Behavior |
|
|
|
-|-------|--------|----------|----------|
|
|
|
-| Search service failure (Phase 2) | 200 | Memo suggestion only | Graceful degradation, log error |
|
|
|
-| GROWI AI failure (Phase 2) | 200 | Memo suggestion only | Graceful degradation, log error |
|
|
|
-| Unexpected error | 500 | Generic error, no internal details | Requirement 9.2 |
|
|
|
+| Error | Status | Response | Requirement |
|
|
|
+|-------|--------|----------|-------------|
|
|
|
+| Unexpected error | 500 | Generic error, no internal details | 9.2 |
|
|
|
|
|
|
-**Key decision**: Phase 2 failures degrade to Phase 1 behavior (memo-only) rather than returning errors. The memo suggestion is always generated first and acts as guaranteed fallback.
|
|
|
+**Key decision**: Phase 2 failures degrade gracefully rather than returning errors. The memo suggestion is generated first and acts as guaranteed fallback. Each Phase 2 component fails independently — content analysis failure skips the entire search pipeline, but category generation can still proceed if it runs independently.
|
|
|
|
|
|
## Testing Strategy
|
|
|
|
|
|
### Unit Tests
|
|
|
|
|
|
-- `MemoSuggestionGenerator`: Generates correct path from username, correct grant value, correct description
|
|
|
-- `DescriptionGenerator`: Fixed text for memo, page title listing for search, segment name for category
|
|
|
-- `GrantResolver`: Returns correct grant from page, default grant when page not found
|
|
|
+- `MemoSuggestionGenerator`: Correct path, grant, description for both user-pages-enabled and disabled cases
|
|
|
+- `ContentAnalyzer`: Correct keyword extraction, flow/stock classification, error propagation
|
|
|
+- `SearchCandidateRetriever`: Score threshold filtering, empty result handling, candidate structure
|
|
|
+- `CandidateEvaluator`: Path proposal patterns (parent/subdirectory/sibling), description generation, ranking, flow/stock consideration, error propagation
|
|
|
+- `CategorySuggestionGenerator`: Top-level extraction, description, grant, empty result handling
|
|
|
+- `GrantResolver`: Returns correct grant from page, default grant when page not found, ancestor traversal for new paths
|
|
|
- `PathSuggestion` type validation: Trailing slash enforcement, required fields present
|
|
|
- Request validation: Missing body, empty body, valid body
|
|
|
|
|
|
@@ -633,19 +787,23 @@ interface SuggestPathResponse {
|
|
|
- `POST /suggest-path` without auth: Returns 401
|
|
|
- `POST /suggest-path` with empty body: Returns 400
|
|
|
- `POST /suggest-path` with AI disabled: Returns 403
|
|
|
-- Phase 2: Search returns results → includes search/category suggestions
|
|
|
-- Phase 2: Search returns nothing → memo-only response
|
|
|
-- Phase 2: AI extraction fails → memo-only fallback
|
|
|
+- Phase 2: Full pipeline — content analysis → search → candidate evaluation → response with multiple suggestion types
|
|
|
+- Phase 2: Content analysis fails → memo-only fallback
|
|
|
+- Phase 2: Search returns nothing → search-based suggestions omitted, category may still appear
|
|
|
+- Phase 2: Candidate evaluation fails → memo + category fallback
|
|
|
+- Phase 2: All Phase 2 components fail → memo-only response
|
|
|
|
|
|
### Performance (Phase 2)
|
|
|
|
|
|
-- Keyword extraction latency under typical content sizes
|
|
|
-- Search query performance with extracted keywords
|
|
|
-- Parallel generation of search + category suggestions
|
|
|
+- Content analysis (1st AI call) latency under typical content sizes
|
|
|
+- Candidate evaluation (2nd AI call) latency with varying numbers of candidates
|
|
|
+- Total end-to-end latency for the 2-AI-call flow
|
|
|
+- Parallel execution: search-evaluate pipeline vs category generation
|
|
|
|
|
|
## Security Considerations
|
|
|
|
|
|
- **Authentication**: All requests require valid API token or login session (standard middleware)
|
|
|
- **Authorization**: User can only see suggestions based on their own identity and permissions. Search results are permission-scoped via `searchKeyword()` user/group parameters
|
|
|
- **Input safety**: Content body is passed to GROWI AI, not directly to Elasticsearch. No NoSQL injection risk from body content
|
|
|
+- **AI prompt injection**: Content body is user-provided and passed to AI. AI prompts should be structured to minimize prompt injection risk (system prompt + user content separation)
|
|
|
- **Information leakage**: Error responses use generic messages per requirement 9.2. No stack traces or internal paths exposed
|